
                    var CLOSURE_DEFINES = {'goog.ENABLE_DEBUG_LOADER': false};
                // Input 0
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Bootstrap for the Google JS Library (Closure).
 *
 * In uncompiled mode base.js will write out Closure's deps file, unless the
 * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
 * include their own deps file(s) from different locations.
 *
 *
 * @provideGoog
 */


/**
 * @define {boolean} Overridden to true by the compiler when --closure_pass
 *     or --mark_as_compiled is specified.
 */
var COMPILED = false;


/**
 * Base namespace for the Closure library.  Checks to see goog is
 * already defined in the current scope before assigning to prevent
 * clobbering if base.js is loaded more than once.
 *
 * @const
 */
var goog = goog || {};


/**
 * Reference to the global context.  In most cases this will be 'window'.
 */
goog.global = this;


/**
 * A hook for overriding the define values in uncompiled mode.
 *
 * In uncompiled mode, {@code CLOSURE_DEFINES} may be defined before loading
 * base.js.  If a key is defined in {@code CLOSURE_DEFINES}, {@code goog.define}
 * will use the value instead of the default value.  This allows flags to be
 * overwritten without compilation (this is normally accomplished with the
 * compiler's "define" flag).
 *
 * Example:
 * <pre>
 *   var CLOSURE_DEFINES = {'goog.DEBUG', false};
 * </pre>
 *
 * @type {Object.<string, (string|number|boolean)>|undefined}
 */
goog.global.CLOSURE_DEFINES;


/**
 * Builds an object structure for the provided namespace path,
 * ensuring that names that already exist are not overwritten. For
 * example:
 * "a.b.c" -> a = {};a.b={};a.b.c={};
 * Used by goog.provide and goog.exportSymbol.
 * @param {string} name name of the object that this file defines.
 * @param {*=} opt_object the object to expose at the end of the path.
 * @param {Object=} opt_objectToExportTo The object to add the path to; default
 *     is |goog.global|.
 * @private
 */
goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
  var parts = name.split('.');
  var cur = opt_objectToExportTo || goog.global;

  // Internet Explorer exhibits strange behavior when throwing errors from
  // methods externed in this manner.  See the testExportSymbolExceptions in
  // base_test.html for an example.
  if (!(parts[0] in cur) && cur.execScript) {
    cur.execScript('var ' + parts[0]);
  }

  // Certain browsers cannot parse code in the form for((a in b); c;);
  // This pattern is produced by the JSCompiler when it collapses the
  // statement above into the conditional loop below. To prevent this from
  // happening, use a for-loop and reserve the init logic as below.

  // Parentheses added to eliminate strict JS warning in Firefox.
  for (var part; parts.length && (part = parts.shift());) {
    if (!parts.length && opt_object !== undefined) {
      // last part and we have an object; use it
      cur[part] = opt_object;
    } else if (cur[part]) {
      cur = cur[part];
    } else {
      cur = cur[part] = {};
    }
  }
};


/**
 * Defines a named value. In uncompiled mode, the value is retreived from
 * CLOSURE_DEFINES if the object is defined and has the property specified,
 * and otherwise used the defined defaultValue. When compiled, the default
 * can be overridden using compiler command-line options.
 *
 * @param {string} name The distinguished name to provide.
 * @param {string|number|boolean} defaultValue
 */
goog.define = function(name, defaultValue) {
  var value = defaultValue;
  if (!COMPILED) {
    if (goog.global.CLOSURE_DEFINES && Object.prototype.hasOwnProperty.call(
        goog.global.CLOSURE_DEFINES, name)) {
      value = goog.global.CLOSURE_DEFINES[name];
    }
  }
  goog.exportPath_(name, value);
};


/**
 * @define {boolean} DEBUG is provided as a convenience so that debugging code
 * that should not be included in a production js_binary can be easily stripped
 * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
 * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
 * because they are generally used for debugging purposes and it is difficult
 * for the JSCompiler to statically determine whether they are used.
 */
goog.DEBUG = true;


/**
 * @define {string} LOCALE defines the locale being used for compilation. It is
 * used to select locale specific data to be compiled in js binary. BUILD rule
 * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
 * option.
 *
 * Take into account that the locale code format is important. You should use
 * the canonical Unicode format with hyphen as a delimiter. Language must be
 * lowercase, Language Script - Capitalized, Region - UPPERCASE.
 * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
 *
 * See more info about locale codes here:
 * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
 *
 * For language codes you should use values defined by ISO 693-1. See it here
 * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
 * this rule: the Hebrew language. For legacy reasons the old code (iw) should
 * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
 */
goog.define('goog.LOCALE', 'en');  // default to en


/**
 * @define {boolean} Whether this code is running on trusted sites.
 *
 * On untrusted sites, several native functions can be defined or overridden by
 * external libraries like Prototype, Datejs, and JQuery and setting this flag
 * to false forces closure to use its own implementations when possible.
 *
 * If your javascript can be loaded by a third party site and you are wary about
 * relying on non-standard implementations, specify
 * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
 */
goog.define('goog.TRUSTED_SITE', true);


/**
 * Creates object stubs for a namespace.  The presence of one or more
 * goog.provide() calls indicate that the file defines the given
 * objects/namespaces.  Build tools also scan for provide/require statements
 * to discern dependencies, build dependency files (see deps.js), etc.
 * @see goog.require
 * @param {string} name Namespace provided by this file in the form
 *     "goog.package.part".
 */
goog.provide = function(name) {
  if (!COMPILED) {
    // Ensure that the same namespace isn't provided twice. This is intended
    // to teach new developers that 'goog.provide' is effectively a variable
    // declaration. And when JSCompiler transforms goog.provide into a real
    // variable declaration, the compiled JS should work the same as the raw
    // JS--even when the raw JS uses goog.provide incorrectly.
    if (goog.isProvided_(name)) {
      throw Error('Namespace "' + name + '" already declared.');
    }
    delete goog.implicitNamespaces_[name];

    var namespace = name;
    while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
      if (goog.getObjectByName(namespace)) {
        break;
      }
      goog.implicitNamespaces_[namespace] = true;
    }
  }

  goog.exportPath_(name);
};


/**
 * Marks that the current file should only be used for testing, and never for
 * live code in production.
 *
 * In the case of unit tests, the message may optionally be an exact
 * namespace for the test (e.g. 'goog.stringTest'). The linter will then
 * ignore the extra provide (if not explicitly defined in the code).
 *
 * @param {string=} opt_message Optional message to add to the error that's
 *     raised when used in production code.
 */
goog.setTestOnly = function(opt_message) {
  if (COMPILED && !goog.DEBUG) {
    opt_message = opt_message || '';
    throw Error('Importing test-only code into non-debug environment' +
                opt_message ? ': ' + opt_message : '.');
  }
};


if (!COMPILED) {

  /**
   * Check if the given name has been goog.provided. This will return false for
   * names that are available only as implicit namespaces.
   * @param {string} name name of the object to look for.
   * @return {boolean} Whether the name has been provided.
   * @private
   */
  goog.isProvided_ = function(name) {
    return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name);
  };

  /**
   * Namespaces implicitly defined by goog.provide. For example,
   * goog.provide('goog.events.Event') implicitly declares
   * that 'goog' and 'goog.events' must be namespaces.
   *
   * @type {Object}
   * @private
   */
  goog.implicitNamespaces_ = {};
}


/**
 * Returns an object based on its fully qualified external name.  If you are
 * using a compilation pass that renames property names beware that using this
 * function will not find renamed properties.
 *
 * @param {string} name The fully qualified name.
 * @param {Object=} opt_obj The object within which to look; default is
 *     |goog.global|.
 * @return {?} The value (object or primitive) or, if not found, null.
 */
goog.getObjectByName = function(name, opt_obj) {
  var parts = name.split('.');
  var cur = opt_obj || goog.global;
  for (var part; part = parts.shift(); ) {
    if (goog.isDefAndNotNull(cur[part])) {
      cur = cur[part];
    } else {
      return null;
    }
  }
  return cur;
};


/**
 * Globalizes a whole namespace, such as goog or goog.lang.
 *
 * @param {Object} obj The namespace to globalize.
 * @param {Object=} opt_global The object to add the properties to.
 * @deprecated Properties may be explicitly exported to the global scope, but
 *     this should no longer be done in bulk.
 */
goog.globalize = function(obj, opt_global) {
  var global = opt_global || goog.global;
  for (var x in obj) {
    global[x] = obj[x];
  }
};


/**
 * Adds a dependency from a file to the files it requires.
 * @param {string} relPath The path to the js file.
 * @param {Array} provides An array of strings with the names of the objects
 *                         this file provides.
 * @param {Array} requires An array of strings with the names of the objects
 *                         this file requires.
 */
goog.addDependency = function(relPath, provides, requires) {
  if (goog.DEPENDENCIES_ENABLED) {
    var provide, require;
    var path = relPath.replace(/\\/g, '/');
    var deps = goog.dependencies_;
    for (var i = 0; provide = provides[i]; i++) {
      deps.nameToPath[provide] = path;
      if (!(path in deps.pathToNames)) {
        deps.pathToNames[path] = {};
      }
      deps.pathToNames[path][provide] = true;
    }
    for (var j = 0; require = requires[j]; j++) {
      if (!(path in deps.requires)) {
        deps.requires[path] = {};
      }
      deps.requires[path][require] = true;
    }
  }
};




// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal
// way to do "debug-mode" development.  The dependency system can sometimes
// be confusing, as can the debug DOM loader's asyncronous nature.
//
// With the DOM loader, a call to goog.require() is not blocking -- the
// script will not load until some point after the current script.  If a
// namespace is needed at runtime, it needs to be defined in a previous
// script, or loaded via require() with its registered dependencies.
// User-defined namespaces may need their own deps file.  See http://go/js_deps,
// http://go/genjsdeps, or, externally, DepsWriter.
// http://code.google.com/closure/library/docs/depswriter.html
//
// Because of legacy clients, the DOM loader can't be easily removed from
// base.js.  Work is being done to make it disableable or replaceable for
// different environments (DOM-less JavaScript interpreters like Rhino or V8,
// for example). See bootstrap/ for more information.


/**
 * @define {boolean} Whether to enable the debug loader.
 *
 * If enabled, a call to goog.require() will attempt to load the namespace by
 * appending a script tag to the DOM (if the namespace has been registered).
 *
 * If disabled, goog.require() will simply assert that the namespace has been
 * provided (and depend on the fact that some outside tool correctly ordered
 * the script).
 */
goog.define('goog.ENABLE_DEBUG_LOADER', true);


/**
 * Implements a system for the dynamic resolution of dependencies
 * that works in parallel with the BUILD system. Note that all calls
 * to goog.require will be stripped by the JSCompiler when the
 * --closure_pass option is used.
 * @see goog.provide
 * @param {string} name Namespace to include (as was given in goog.provide())
 *     in the form "goog.package.part".
 */
goog.require = function(name) {

  // if the object already exists we do not need do do anything
  // TODO(arv): If we start to support require based on file name this has
  //            to change
  // TODO(arv): If we allow goog.foo.* this has to change
  // TODO(arv): If we implement dynamic load after page load we should probably
  //            not remove this code for the compiled output
  if (!COMPILED) {
    if (goog.isProvided_(name)) {
      return;
    }

    if (goog.ENABLE_DEBUG_LOADER) {
      var path = goog.getPathFromDeps_(name);
      if (path) {
        goog.included_[path] = true;
        goog.writeScripts_();
        return;
      }
    }

    var errorMessage = 'goog.require could not find: ' + name;
    if (goog.global.console) {
      goog.global.console['error'](errorMessage);
    }


      throw Error(errorMessage);

  }
};


/**
 * Path for included scripts
 * @type {string}
 */
goog.basePath = '';


/**
 * A hook for overriding the base path.
 * @type {string|undefined}
 */
goog.global.CLOSURE_BASE_PATH;


/**
 * Whether to write out Closure's deps file. By default,
 * the deps are written.
 * @type {boolean|undefined}
 */
goog.global.CLOSURE_NO_DEPS;


/**
 * A function to import a single script. This is meant to be overridden when
 * Closure is being run in non-HTML contexts, such as web workers. It's defined
 * in the global scope so that it can be set before base.js is loaded, which
 * allows deps.js to be imported properly.
 *
 * The function is passed the script source, which is a relative URI. It should
 * return true if the script was imported, false otherwise.
 */
goog.global.CLOSURE_IMPORT_SCRIPT;


/**
 * Null function used for default values of callbacks, etc.
 * @return {void} Nothing.
 */
goog.nullFunction = function() {};


/**
 * The identity function. Returns its first argument.
 *
 * @param {*=} opt_returnValue The single value that will be returned.
 * @param {...*} var_args Optional trailing arguments. These are ignored.
 * @return {?} The first argument. We can't know the type -- just pass it along
 *      without type.
 * @deprecated Use goog.functions.identity instead.
 */
goog.identityFunction = function(opt_returnValue, var_args) {
  return opt_returnValue;
};


/**
 * When defining a class Foo with an abstract method bar(), you can do:
 *
 * Foo.prototype.bar = goog.abstractMethod
 *
 * Now if a subclass of Foo fails to override bar(), an error
 * will be thrown when bar() is invoked.
 *
 * Note: This does not take the name of the function to override as
 * an argument because that would make it more difficult to obfuscate
 * our JavaScript code.
 *
 * @type {!Function}
 * @throws {Error} when invoked to indicate the method should be
 *   overridden.
 */
goog.abstractMethod = function() {
  throw Error('unimplemented abstract method');
};


/**
 * Adds a {@code getInstance} static method that always return the same instance
 * object.
 * @param {!Function} ctor The constructor for the class to add the static
 *     method to.
 */
goog.addSingletonGetter = function(ctor) {
  ctor.getInstance = function() {
    if (ctor.instance_) {
      return ctor.instance_;
    }
    if (goog.DEBUG) {
      // NOTE: JSCompiler can't optimize away Array#push.
      goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
    }
    return ctor.instance_ = new ctor;
  };
};


/**
 * All singleton classes that have been instantiated, for testing. Don't read
 * it directly, use the {@code goog.testing.singleton} module. The compiler
 * removes this variable if unused.
 * @type {!Array.<!Function>}
 * @private
 */
goog.instantiatedSingletons_ = [];


/**
 * True if goog.dependencies_ is available.
 * @const {boolean}
 */
goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;


if (goog.DEPENDENCIES_ENABLED) {
  /**
   * Object used to keep track of urls that have already been added. This
   * record allows the prevention of circular dependencies.
   * @type {Object}
   * @private
   */
  goog.included_ = {};


  /**
   * This object is used to keep track of dependencies and other data that is
   * used for loading scripts
   * @private
   * @type {Object}
   */
  goog.dependencies_ = {
    pathToNames: {}, // 1 to many
    nameToPath: {}, // 1 to 1
    requires: {}, // 1 to many
    // used when resolving dependencies to prevent us from
    // visiting the file twice
    visited: {},
    written: {} // used to keep track of script files we have written
  };


  /**
   * Tries to detect whether is in the context of an HTML document.
   * @return {boolean} True if it looks like HTML document.
   * @private
   */
  goog.inHtmlDocument_ = function() {
    var doc = goog.global.document;
    return typeof doc != 'undefined' &&
           'write' in doc;  // XULDocument misses write.
  };


  /**
   * Tries to detect the base path of the base.js script that bootstraps Closure
   * @private
   */
  goog.findBasePath_ = function() {
    if (goog.global.CLOSURE_BASE_PATH) {
      goog.basePath = goog.global.CLOSURE_BASE_PATH;
      return;
    } else if (!goog.inHtmlDocument_()) {
      return;
    }
    var doc = goog.global.document;
    var scripts = doc.getElementsByTagName('script');
    // Search backwards since the current script is in almost all cases the one
    // that has base.js.
    for (var i = scripts.length - 1; i >= 0; --i) {
      var src = scripts[i].src;
      var qmark = src.lastIndexOf('?');
      var l = qmark == -1 ? src.length : qmark;
      if (src.substr(l - 7, 7) == 'base.js') {
        goog.basePath = src.substr(0, l - 7);
        return;
      }
    }
  };


  /**
   * Imports a script if, and only if, that script hasn't already been imported.
   * (Must be called at execution time)
   * @param {string} src Script source.
   * @private
   */
  goog.importScript_ = function(src) {
    var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
        goog.writeScriptTag_;
    if (!goog.dependencies_.written[src] && importScript(src)) {
      goog.dependencies_.written[src] = true;
    }
  };


  /**
   * The default implementation of the import function. Writes a script tag to
   * import the script.
   *
   * @param {string} src The script source.
   * @return {boolean} True if the script was imported, false otherwise.
   * @private
   */
  goog.writeScriptTag_ = function(src) {
    if (goog.inHtmlDocument_()) {
      var doc = goog.global.document;

      // If the user tries to require a new symbol after document load,
      // something has gone terribly wrong. Doing a document.write would
      // wipe out the page.
      if (doc.readyState == 'complete') {
        // Certain test frameworks load base.js multiple times, which tries
        // to write deps.js each time. If that happens, just fail silently.
        // These frameworks wipe the page between each load of base.js, so this
        // is OK.
        var isDeps = /\bdeps.js$/.test(src);
        if (isDeps) {
          return false;
        } else {
          throw Error('Cannot write "' + src + '" after document load');
        }
      }

      doc.write(
          '<script type="text/javascript" src="' + src + '"></' + 'script>');
      return true;
    } else {
      return false;
    }
  };


  /**
   * Resolves dependencies based on the dependencies added using addDependency
   * and calls importScript_ in the correct order.
   * @private
   */
  goog.writeScripts_ = function() {
    // the scripts we need to write this time
    var scripts = [];
    var seenScript = {};
    var deps = goog.dependencies_;

    function visitNode(path) {
      if (path in deps.written) {
        return;
      }

      // we have already visited this one. We can get here if we have cyclic
      // dependencies
      if (path in deps.visited) {
        if (!(path in seenScript)) {
          seenScript[path] = true;
          scripts.push(path);
        }
        return;
      }

      deps.visited[path] = true;

      if (path in deps.requires) {
        for (var requireName in deps.requires[path]) {
          // If the required name is defined, we assume that it was already
          // bootstrapped by other means.
          if (!goog.isProvided_(requireName)) {
            if (requireName in deps.nameToPath) {
              visitNode(deps.nameToPath[requireName]);
            } else {
              throw Error('Undefined nameToPath for ' + requireName);
            }
          }
        }
      }

      if (!(path in seenScript)) {
        seenScript[path] = true;
        scripts.push(path);
      }
    }

    for (var path in goog.included_) {
      if (!deps.written[path]) {
        visitNode(path);
      }
    }

    for (var i = 0; i < scripts.length; i++) {
      if (scripts[i]) {
        goog.importScript_(goog.basePath + scripts[i]);
      } else {
        throw Error('Undefined script input');
      }
    }
  };


  /**
   * Looks at the dependency rules and tries to determine the script file that
   * fulfills a particular rule.
   * @param {string} rule In the form goog.namespace.Class or project.script.
   * @return {?string} Url corresponding to the rule, or null.
   * @private
   */
  goog.getPathFromDeps_ = function(rule) {
    if (rule in goog.dependencies_.nameToPath) {
      return goog.dependencies_.nameToPath[rule];
    } else {
      return null;
    }
  };

  goog.findBasePath_();

  // Allow projects to manage the deps files themselves.
  if (!goog.global.CLOSURE_NO_DEPS) {
    goog.importScript_(goog.basePath + 'deps.js');
  }
}



//==============================================================================
// Language Enhancements
//==============================================================================


/**
 * This is a "fixed" version of the typeof operator.  It differs from the typeof
 * operator in such a way that null returns 'null' and arrays return 'array'.
 * @param {*} value The value to get the type of.
 * @return {string} The name of the type.
 */
goog.typeOf = function(value) {
  var s = typeof value;
  if (s == 'object') {
    if (value) {
      // Check these first, so we can avoid calling Object.prototype.toString if
      // possible.
      //
      // IE improperly marshals tyepof across execution contexts, but a
      // cross-context object will still return false for "instanceof Object".
      if (value instanceof Array) {
        return 'array';
      } else if (value instanceof Object) {
        return s;
      }

      // HACK: In order to use an Object prototype method on the arbitrary
      //   value, the compiler requires the value be cast to type Object,
      //   even though the ECMA spec explicitly allows it.
      var className = Object.prototype.toString.call(
          /** @type {Object} */ (value));
      // In Firefox 3.6, attempting to access iframe window objects' length
      // property throws an NS_ERROR_FAILURE, so we need to special-case it
      // here.
      if (className == '[object Window]') {
        return 'object';
      }

      // We cannot always use constructor == Array or instanceof Array because
      // different frames have different Array objects. In IE6, if the iframe
      // where the array was created is destroyed, the array loses its
      // prototype. Then dereferencing val.splice here throws an exception, so
      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
      // so that will work. In this case, this function will return false and
      // most array functions will still work because the array is still
      // array-like (supports length and []) even though it has lost its
      // prototype.
      // Mark Miller noticed that Object.prototype.toString
      // allows access to the unforgeable [[Class]] property.
      //  15.2.4.2 Object.prototype.toString ( )
      //  When the toString method is called, the following steps are taken:
      //      1. Get the [[Class]] property of this object.
      //      2. Compute a string value by concatenating the three strings
      //         "[object ", Result(1), and "]".
      //      3. Return Result(2).
      // and this behavior survives the destruction of the execution context.
      if ((className == '[object Array]' ||
           // In IE all non value types are wrapped as objects across window
           // boundaries (not iframe though) so we have to do object detection
           // for this edge case
           typeof value.length == 'number' &&
           typeof value.splice != 'undefined' &&
           typeof value.propertyIsEnumerable != 'undefined' &&
           !value.propertyIsEnumerable('splice')

          )) {
        return 'array';
      }
      // HACK: There is still an array case that fails.
      //     function ArrayImpostor() {}
      //     ArrayImpostor.prototype = [];
      //     var impostor = new ArrayImpostor;
      // this can be fixed by getting rid of the fast path
      // (value instanceof Array) and solely relying on
      // (value && Object.prototype.toString.vall(value) === '[object Array]')
      // but that would require many more function calls and is not warranted
      // unless closure code is receiving objects from untrusted sources.

      // IE in cross-window calls does not correctly marshal the function type
      // (it appears just as an object) so we cannot use just typeof val ==
      // 'function'. However, if the object has a call property, it is a
      // function.
      if ((className == '[object Function]' ||
          typeof value.call != 'undefined' &&
          typeof value.propertyIsEnumerable != 'undefined' &&
          !value.propertyIsEnumerable('call'))) {
        return 'function';
      }


    } else {
      return 'null';
    }

  } else if (s == 'function' && typeof value.call == 'undefined') {
    // In Safari typeof nodeList returns 'function', and on Firefox
    // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
    // and RegExps.  We would like to return object for those and we can
    // detect an invalid function by making sure that the function
    // object has a call method.
    return 'object';
  }
  return s;
};


/**
 * Returns true if the specified value is not |undefined|.
 * WARNING: Do not use this to test if an object has a property. Use the in
 * operator instead.  Additionally, this function assumes that the global
 * undefined variable has not been redefined.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is defined.
 */
goog.isDef = function(val) {
  return val !== undefined;
};


/**
 * Returns true if the specified value is |null|
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is null.
 */
goog.isNull = function(val) {
  return val === null;
};


/**
 * Returns true if the specified value is defined and not null
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is defined and not null.
 */
goog.isDefAndNotNull = function(val) {
  // Note that undefined == null.
  return val != null;
};


/**
 * Returns true if the specified value is an array
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArray = function(val) {
  return goog.typeOf(val) == 'array';
};


/**
 * Returns true if the object looks like an array. To qualify as array like
 * the value needs to be either a NodeList or an object with a Number length
 * property.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArrayLike = function(val) {
  var type = goog.typeOf(val);
  return type == 'array' || type == 'object' && typeof val.length == 'number';
};


/**
 * Returns true if the object looks like a Date. To qualify as Date-like
 * the value needs to be an object and have a getFullYear() function.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a like a Date.
 */
goog.isDateLike = function(val) {
  return goog.isObject(val) && typeof val.getFullYear == 'function';
};


/**
 * Returns true if the specified value is a string
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a string.
 */
goog.isString = function(val) {
  return typeof val == 'string';
};


/**
 * Returns true if the specified value is a boolean
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is boolean.
 */
goog.isBoolean = function(val) {
  return typeof val == 'boolean';
};


/**
 * Returns true if the specified value is a number
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a number.
 */
goog.isNumber = function(val) {
  return typeof val == 'number';
};


/**
 * Returns true if the specified value is a function
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is a function.
 */
goog.isFunction = function(val) {
  return goog.typeOf(val) == 'function';
};


/**
 * Returns true if the specified value is an object.  This includes arrays
 * and functions.
 * @param {*} val Variable to test.
 * @return {boolean} Whether variable is an object.
 */
goog.isObject = function(val) {
  var type = typeof val;
  return type == 'object' && val != null || type == 'function';
  // return Object(val) === val also works, but is slower, especially if val is
  // not an object.
};


/**
 * Gets a unique ID for an object. This mutates the object so that further
 * calls with the same object as a parameter returns the same value. The unique
 * ID is guaranteed to be unique across the current session amongst objects that
 * are passed into {@code getUid}. There is no guarantee that the ID is unique
 * or consistent across sessions. It is unsafe to generate unique ID for
 * function prototypes.
 *
 * @param {Object} obj The object to get the unique ID for.
 * @return {number} The unique ID for the object.
 */
goog.getUid = function(obj) {
  // TODO(arv): Make the type stricter, do not accept null.

  // In Opera window.hasOwnProperty exists but always returns false so we avoid
  // using it. As a consequence the unique ID generated for BaseClass.prototype
  // and SubClass.prototype will be the same.
  return obj[goog.UID_PROPERTY_] ||
      (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
};


/**
 * Removes the unique ID from an object. This is useful if the object was
 * previously mutated using {@code goog.getUid} in which case the mutation is
 * undone.
 * @param {Object} obj The object to remove the unique ID field from.
 */
goog.removeUid = function(obj) {
  // TODO(arv): Make the type stricter, do not accept null.

  // DOM nodes in IE are not instance of Object and throws exception
  // for delete. Instead we try to use removeAttribute
  if ('removeAttribute' in obj) {
    obj.removeAttribute(goog.UID_PROPERTY_);
  }
  /** @preserveTry */
  try {
    delete obj[goog.UID_PROPERTY_];
  } catch (ex) {
  }
};


/**
 * Name for unique ID property. Initialized in a way to help avoid collisions
 * with other closure javascript on the same page.
 * @type {string}
 * @private
 */
goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);


/**
 * Counter for UID.
 * @type {number}
 * @private
 */
goog.uidCounter_ = 0;


/**
 * Adds a hash code field to an object. The hash code is unique for the
 * given object.
 * @param {Object} obj The object to get the hash code for.
 * @return {number} The hash code for the object.
 * @deprecated Use goog.getUid instead.
 */
goog.getHashCode = goog.getUid;


/**
 * Removes the hash code field from an object.
 * @param {Object} obj The object to remove the field from.
 * @deprecated Use goog.removeUid instead.
 */
goog.removeHashCode = goog.removeUid;


/**
 * Clones a value. The input may be an Object, Array, or basic type. Objects and
 * arrays will be cloned recursively.
 *
 * WARNINGS:
 * <code>goog.cloneObject</code> does not detect reference loops. Objects that
 * refer to themselves will cause infinite recursion.
 *
 * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
 * UIDs created by <code>getUid</code> into cloned results.
 *
 * @param {*} obj The value to clone.
 * @return {*} A clone of the input value.
 * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
 */
goog.cloneObject = function(obj) {
  var type = goog.typeOf(obj);
  if (type == 'object' || type == 'array') {
    if (obj.clone) {
      return obj.clone();
    }
    var clone = type == 'array' ? [] : {};
    for (var key in obj) {
      clone[key] = goog.cloneObject(obj[key]);
    }
    return clone;
  }

  return obj;
};


/**
 * A native implementation of goog.bind.
 * @param {Function} fn A function to partially apply.
 * @param {Object|undefined} selfObj Specifies the object which |this| should
 *     point to when the function is run.
 * @param {...*} var_args Additional arguments that are partially
 *     applied to the function.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 * @private
 * @suppress {deprecated} The compiler thinks that Function.prototype.bind
 *     is deprecated because some people have declared a pure-JS version.
 *     Only the pure-JS version is truly deprecated.
 */
goog.bindNative_ = function(fn, selfObj, var_args) {
  return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
};


/**
 * A pure-JS implementation of goog.bind.
 * @param {Function} fn A function to partially apply.
 * @param {Object|undefined} selfObj Specifies the object which |this| should
 *     point to when the function is run.
 * @param {...*} var_args Additional arguments that are partially
 *     applied to the function.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 * @private
 */
goog.bindJs_ = function(fn, selfObj, var_args) {
  if (!fn) {
    throw new Error();
  }

  if (arguments.length > 2) {
    var boundArgs = Array.prototype.slice.call(arguments, 2);
    return function() {
      // Prepend the bound arguments to the current arguments.
      var newArgs = Array.prototype.slice.call(arguments);
      Array.prototype.unshift.apply(newArgs, boundArgs);
      return fn.apply(selfObj, newArgs);
    };

  } else {
    return function() {
      return fn.apply(selfObj, arguments);
    };
  }
};


/**
 * Partially applies this function to a particular 'this object' and zero or
 * more arguments. The result is a new function with some arguments of the first
 * function pre-filled and the value of |this| 'pre-specified'.<br><br>
 *
 * Remaining arguments specified at call-time are appended to the pre-
 * specified ones.<br><br>
 *
 * Also see: {@link #partial}.<br><br>
 *
 * Usage:
 * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
 * barMethBound('arg3', 'arg4');</pre>
 *
 * @param {?function(this:T, ...)} fn A function to partially apply.
 * @param {T} selfObj Specifies the object which |this| should
 *     point to when the function is run.
 * @param {...*} var_args Additional arguments that are partially
 *     applied to the function.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 * @template T
 * @suppress {deprecated} See above.
 */
goog.bind = function(fn, selfObj, var_args) {
  // TODO(nicksantos): narrow the type signature.
  if (Function.prototype.bind &&
      // NOTE(nicksantos): Somebody pulled base.js into the default
      // Chrome extension environment. This means that for Chrome extensions,
      // they get the implementation of Function.prototype.bind that
      // calls goog.bind instead of the native one. Even worse, we don't want
      // to introduce a circular dependency between goog.bind and
      // Function.prototype.bind, so we have to hack this to make sure it
      // works correctly.
      Function.prototype.bind.toString().indexOf('native code') != -1) {
    goog.bind = goog.bindNative_;
  } else {
    goog.bind = goog.bindJs_;
  }
  return goog.bind.apply(null, arguments);
};


/**
 * Like bind(), except that a 'this object' is not required. Useful when the
 * target function is already bound.
 *
 * Usage:
 * var g = partial(f, arg1, arg2);
 * g(arg3, arg4);
 *
 * @param {Function} fn A function to partially apply.
 * @param {...*} var_args Additional arguments that are partially
 *     applied to fn.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 */
goog.partial = function(fn, var_args) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    // Prepend the bound arguments to the current arguments.
    var newArgs = Array.prototype.slice.call(arguments);
    newArgs.unshift.apply(newArgs, args);
    return fn.apply(this, newArgs);
  };
};


/**
 * Copies all the members of a source object to a target object. This method
 * does not work on all browsers for all objects that contain keys such as
 * toString or hasOwnProperty. Use goog.object.extend for this purpose.
 * @param {Object} target Target.
 * @param {Object} source Source.
 */
goog.mixin = function(target, source) {
  for (var x in source) {
    target[x] = source[x];
  }

  // For IE7 or lower, the for-in-loop does not contain any properties that are
  // not enumerable on the prototype object (for example, isPrototypeOf from
  // Object.prototype) but also it will not include 'replace' on objects that
  // extend String and change 'replace' (not that it is common for anyone to
  // extend anything except Object).
};


/**
 * @return {number} An integer value representing the number of milliseconds
 *     between midnight, January 1, 1970 and the current time.
 */
goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
  // Unary plus operator converts its operand to a number which in the case of
  // a date is done by calling getTime().
  return +new Date();
});


/**
 * Evals javascript in the global scope.  In IE this uses execScript, other
 * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
 * global scope (for example, in Safari), appends a script tag instead.
 * Throws an exception if neither execScript or eval is defined.
 * @param {string} script JavaScript string.
 */
goog.globalEval = function(script) {
  if (goog.global.execScript) {
    goog.global.execScript(script, 'JavaScript');
  } else if (goog.global.eval) {
    // Test to see if eval works
    if (goog.evalWorksForGlobals_ == null) {
      goog.global.eval('var _et_ = 1;');
      if (typeof goog.global['_et_'] != 'undefined') {
        delete goog.global['_et_'];
        goog.evalWorksForGlobals_ = true;
      } else {
        goog.evalWorksForGlobals_ = false;
      }
    }

    if (goog.evalWorksForGlobals_) {
      goog.global.eval(script);
    } else {
      var doc = goog.global.document;
      var scriptElt = doc.createElement('script');
      scriptElt.type = 'text/javascript';
      scriptElt.defer = false;
      // Note(user): can't use .innerHTML since "t('<test>')" will fail and
      // .text doesn't work in Safari 2.  Therefore we append a text node.
      scriptElt.appendChild(doc.createTextNode(script));
      doc.body.appendChild(scriptElt);
      doc.body.removeChild(scriptElt);
    }
  } else {
    throw Error('goog.globalEval not available');
  }
};


/**
 * Indicates whether or not we can call 'eval' directly to eval code in the
 * global scope. Set to a Boolean by the first call to goog.globalEval (which
 * empirically tests whether eval works for globals). @see goog.globalEval
 * @type {?boolean}
 * @private
 */
goog.evalWorksForGlobals_ = null;


/**
 * Optional map of CSS class names to obfuscated names used with
 * goog.getCssName().
 * @type {Object|undefined}
 * @private
 * @see goog.setCssNameMapping
 */
goog.cssNameMapping_;


/**
 * Optional obfuscation style for CSS class names. Should be set to either
 * 'BY_WHOLE' or 'BY_PART' if defined.
 * @type {string|undefined}
 * @private
 * @see goog.setCssNameMapping
 */
goog.cssNameMappingStyle_;


/**
 * Handles strings that are intended to be used as CSS class names.
 *
 * This function works in tandem with @see goog.setCssNameMapping.
 *
 * Without any mapping set, the arguments are simple joined with a
 * hyphen and passed through unaltered.
 *
 * When there is a mapping, there are two possible styles in which
 * these mappings are used. In the BY_PART style, each part (i.e. in
 * between hyphens) of the passed in css name is rewritten according
 * to the map. In the BY_WHOLE style, the full css name is looked up in
 * the map directly. If a rewrite is not specified by the map, the
 * compiler will output a warning.
 *
 * When the mapping is passed to the compiler, it will replace calls
 * to goog.getCssName with the strings from the mapping, e.g.
 *     var x = goog.getCssName('foo');
 *     var y = goog.getCssName(this.baseClass, 'active');
 *  becomes:
 *     var x= 'foo';
 *     var y = this.baseClass + '-active';
 *
 * If one argument is passed it will be processed, if two are passed
 * only the modifier will be processed, as it is assumed the first
 * argument was generated as a result of calling goog.getCssName.
 *
 * @param {string} className The class name.
 * @param {string=} opt_modifier A modifier to be appended to the class name.
 * @return {string} The class name or the concatenation of the class name and
 *     the modifier.
 */
goog.getCssName = function(className, opt_modifier) {
  var getMapping = function(cssName) {
    return goog.cssNameMapping_[cssName] || cssName;
  };

  var renameByParts = function(cssName) {
    // Remap all the parts individually.
    var parts = cssName.split('-');
    var mapped = [];
    for (var i = 0; i < parts.length; i++) {
      mapped.push(getMapping(parts[i]));
    }
    return mapped.join('-');
  };

  var rename;
  if (goog.cssNameMapping_) {
    rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
        getMapping : renameByParts;
  } else {
    rename = function(a) {
      return a;
    };
  }

  if (opt_modifier) {
    return className + '-' + rename(opt_modifier);
  } else {
    return rename(className);
  }
};


/**
 * Sets the map to check when returning a value from goog.getCssName(). Example:
 * <pre>
 * goog.setCssNameMapping({
 *   "goog": "a",
 *   "disabled": "b",
 * });
 *
 * var x = goog.getCssName('goog');
 * // The following evaluates to: "a a-b".
 * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
 * </pre>
 * When declared as a map of string literals to string literals, the JSCompiler
 * will replace all calls to goog.getCssName() using the supplied map if the
 * --closure_pass flag is set.
 *
 * @param {!Object} mapping A map of strings to strings where keys are possible
 *     arguments to goog.getCssName() and values are the corresponding values
 *     that should be returned.
 * @param {string=} opt_style The style of css name mapping. There are two valid
 *     options: 'BY_PART', and 'BY_WHOLE'.
 * @see goog.getCssName for a description.
 */
goog.setCssNameMapping = function(mapping, opt_style) {
  goog.cssNameMapping_ = mapping;
  goog.cssNameMappingStyle_ = opt_style;
};


/**
 * To use CSS renaming in compiled mode, one of the input files should have a
 * call to goog.setCssNameMapping() with an object literal that the JSCompiler
 * can extract and use to replace all calls to goog.getCssName(). In uncompiled
 * mode, JavaScript code should be loaded before this base.js file that declares
 * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
 * to ensure that the mapping is loaded before any calls to goog.getCssName()
 * are made in uncompiled mode.
 *
 * A hook for overriding the CSS name mapping.
 * @type {Object|undefined}
 */
goog.global.CLOSURE_CSS_NAME_MAPPING;


if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
  // This does not call goog.setCssNameMapping() because the JSCompiler
  // requires that goog.setCssNameMapping() be called with an object literal.
  goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
}


/**
 * Gets a localized message.
 *
 * This function is a compiler primitive. If you give the compiler a localized
 * message bundle, it will replace the string at compile-time with a localized
 * version, and expand goog.getMsg call to a concatenated string.
 *
 * Messages must be initialized in the form:
 * <code>
 * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
 * </code>
 *
 * @param {string} str Translatable string, places holders in the form {$foo}.
 * @param {Object=} opt_values Map of place holder name to value.
 * @return {string} message with placeholders filled.
 */
goog.getMsg = function(str, opt_values) {
  var values = opt_values || {};
  for (var key in values) {
    var value = ('' + values[key]).replace(/\$/g, '$$$$');
    str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), value);
  }
  return str;
};


/**
 * Gets a localized message. If the message does not have a translation, gives a
 * fallback message.
 *
 * This is useful when introducing a new message that has not yet been
 * translated into all languages.
 *
 * This function is a compiler primtive. Must be used in the form:
 * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
 * where MSG_A and MSG_B were initialized with goog.getMsg.
 *
 * @param {string} a The preferred message.
 * @param {string} b The fallback message.
 * @return {string} The best translated message.
 */
goog.getMsgWithFallback = function(a, b) {
  return a;
};


/**
 * Exposes an unobfuscated global namespace path for the given object.
 * Note that fields of the exported object *will* be obfuscated,
 * unless they are exported in turn via this function or
 * goog.exportProperty
 *
 * <p>Also handy for making public items that are defined in anonymous
 * closures.
 *
 * ex. goog.exportSymbol('public.path.Foo', Foo);
 *
 * ex. goog.exportSymbol('public.path.Foo.staticFunction',
 *                       Foo.staticFunction);
 *     public.path.Foo.staticFunction();
 *
 * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
 *                       Foo.prototype.myMethod);
 *     new public.path.Foo().myMethod();
 *
 * @param {string} publicPath Unobfuscated name to export.
 * @param {*} object Object the name should point to.
 * @param {Object=} opt_objectToExportTo The object to add the path to; default
 *     is |goog.global|.
 */
goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
  goog.exportPath_(publicPath, object, opt_objectToExportTo);
};


/**
 * Exports a property unobfuscated into the object's namespace.
 * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
 * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
 * @param {Object} object Object whose static property is being exported.
 * @param {string} publicName Unobfuscated name to export.
 * @param {*} symbol Object the name should point to.
 */
goog.exportProperty = function(object, publicName, symbol) {
  object[publicName] = symbol;
};


/**
 * Inherit the prototype methods from one constructor into another.
 *
 * Usage:
 * <pre>
 * function ParentClass(a, b) { }
 * ParentClass.prototype.foo = function(a) { }
 *
 * function ChildClass(a, b, c) {
 *   goog.base(this, a, b);
 * }
 * goog.inherits(ChildClass, ParentClass);
 *
 * var child = new ChildClass('a', 'b', 'see');
 * child.foo(); // works
 * </pre>
 *
 * In addition, a superclass' implementation of a method can be invoked
 * as follows:
 *
 * <pre>
 * ChildClass.prototype.foo = function(a) {
 *   ChildClass.superClass_.foo.call(this, a);
 *   // other code
 * };
 * </pre>
 *
 * @param {Function} childCtor Child class.
 * @param {Function} parentCtor Parent class.
 */
goog.inherits = function(childCtor, parentCtor) {
  /** @constructor */
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.superClass_ = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  /** @override */
  childCtor.prototype.constructor = childCtor;
};


/**
 * Call up to the superclass.
 *
 * If this is called from a constructor, then this calls the superclass
 * contructor with arguments 1-N.
 *
 * If this is called from a prototype method, then you must pass
 * the name of the method as the second argument to this function. If
 * you do not, you will get a runtime error. This calls the superclass'
 * method with arguments 2-N.
 *
 * This function only works if you use goog.inherits to express
 * inheritance relationships between your classes.
 *
 * This function is a compiler primitive. At compile-time, the
 * compiler will do macro expansion to remove a lot of
 * the extra overhead that this function introduces. The compiler
 * will also enforce a lot of the assumptions that this function
 * makes, and treat it as a compiler error if you break them.
 *
 * @param {!Object} me Should always be "this".
 * @param {*=} opt_methodName The method name if calling a super method.
 * @param {...*} var_args The rest of the arguments.
 * @return {*} The return value of the superclass method.
 */
goog.base = function(me, opt_methodName, var_args) {
  var caller = arguments.callee.caller;

  if (goog.DEBUG) {
    if (!caller) {
      throw Error('arguments.caller not defined.  goog.base() expects not ' +
                  'to be running in strict mode. See ' +
                  'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
    }
  }

  if (caller.superClass_) {
    // This is a constructor. Call the superclass constructor.
    return caller.superClass_.constructor.apply(
        me, Array.prototype.slice.call(arguments, 1));
  }

  var args = Array.prototype.slice.call(arguments, 2);
  var foundCaller = false;
  for (var ctor = me.constructor;
       ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
    if (ctor.prototype[opt_methodName] === caller) {
      foundCaller = true;
    } else if (foundCaller) {
      return ctor.prototype[opt_methodName].apply(me, args);
    }
  }

  // If we did not find the caller in the prototype chain,
  // then one of two things happened:
  // 1) The caller is an instance method.
  // 2) This method was not called by the right caller.
  if (me[opt_methodName] === caller) {
    return me.constructor.prototype[opt_methodName].apply(me, args);
  } else {
    throw Error(
        'goog.base called from a method of one name ' +
        'to a method of a different name');
  }
};


/**
 * Allow for aliasing within scope functions.  This function exists for
 * uncompiled code - in compiled code the calls will be inlined and the
 * aliases applied.  In uncompiled code the function is simply run since the
 * aliases as written are valid JavaScript.
 * @param {function()} fn Function to call.  This function can contain aliases
 *     to namespaces (e.g. "var dom = goog.dom") or classes
 *    (e.g. "var Timer = goog.Timer").
 */
goog.scope = function(fn) {
  fn.call(goog.global);
};



// Input 1
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Provides a base class for custom Error objects such that the
 * stack is correctly maintained.
 *
 * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
 * sufficient.
 *
 */

goog.provide('goog.debug.Error');



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
goog.debug.Error = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, goog.debug.Error);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
};
goog.inherits(goog.debug.Error, Error);


/** @override */
goog.debug.Error.prototype.name = 'CustomError';

// Input 2
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for string manipulation.
 */


/**
 * Namespace for string utilities
 */
goog.provide('goog.string');
goog.provide('goog.string.Unicode');


/**
 * Common Unicode string characters.
 * @enum {string}
 */
goog.string.Unicode = {
  NBSP: '\xa0'
};


/**
 * Fast prefix-checker.
 * @param {string} str The string to check.
 * @param {string} prefix A string to look for at the start of {@code str}.
 * @return {boolean} True if {@code str} begins with {@code prefix}.
 */
goog.string.startsWith = function(str, prefix) {
  return str.lastIndexOf(prefix, 0) == 0;
};


/**
 * Fast suffix-checker.
 * @param {string} str The string to check.
 * @param {string} suffix A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} ends with {@code suffix}.
 */
goog.string.endsWith = function(str, suffix) {
  var l = str.length - suffix.length;
  return l >= 0 && str.indexOf(suffix, l) == l;
};


/**
 * Case-insensitive prefix-checker.
 * @param {string} str The string to check.
 * @param {string} prefix  A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
 *     case).
 */
goog.string.caseInsensitiveStartsWith = function(str, prefix) {
  return goog.string.caseInsensitiveCompare(
      prefix, str.substr(0, prefix.length)) == 0;
};


/**
 * Case-insensitive suffix-checker.
 * @param {string} str The string to check.
 * @param {string} suffix A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
 *     case).
 */
goog.string.caseInsensitiveEndsWith = function(str, suffix) {
  return goog.string.caseInsensitiveCompare(
      suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
};


/**
 * Case-insensitive equality checker.
 * @param {string} str1 First string to check.
 * @param {string} str2 Second string to check.
 * @return {boolean} True if {@code str1} and {@code str2} are the same string,
 *     ignoring case.
 */
goog.string.caseInsensitiveEquals = function(str1, str2) {
  return str1.toLowerCase() == str2.toLowerCase();
};


/**
 * Does simple python-style string substitution.
 * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
 * @param {string} str The string containing the pattern.
 * @param {...*} var_args The items to substitute into the pattern.
 * @return {string} A copy of {@code str} in which each occurrence of
 *     {@code %s} has been replaced an argument from {@code var_args}.
 */
goog.string.subs = function(str, var_args) {
  // This appears to be slow, but testing shows it compares more or less
  // equivalent to the regex.exec method.
  for (var i = 1; i < arguments.length; i++) {
    // We cast to String in case an argument is a Function.  Replacing $&, for
    // example, with $$$& stops the replace from subsituting the whole match
    // into the resultant string.  $$$& in the first replace becomes $$& in the
    //  second, which leaves $& in the resultant string.  Also:
    // $$, $`, $', $n $nn
    var replacement = String(arguments[i]).replace(/\$/g, '$$$$');
    str = str.replace(/\%s/, replacement);
  }
  return str;
};


/**
 * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
 * and tabs) to a single space, and strips leading and trailing whitespace.
 * @param {string} str Input string.
 * @return {string} A copy of {@code str} with collapsed whitespace.
 */
goog.string.collapseWhitespace = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
};


/**
 * Checks if a string is empty or contains only whitespaces.
 * @param {string} str The string to check.
 * @return {boolean} True if {@code str} is empty or whitespace only.
 */
goog.string.isEmpty = function(str) {
  // testing length == 0 first is actually slower in all browsers (about the
  // same in Opera).
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return /^[\s\xa0]*$/.test(str);
};


/**
 * Checks if a string is null, undefined, empty or contains only whitespaces.
 * @param {*} str The string to check.
 * @return {boolean} True if{@code str} is null, undefined, empty, or
 *     whitespace only.
 */
goog.string.isEmptySafe = function(str) {
  return goog.string.isEmpty(goog.string.makeSafe(str));
};


/**
 * Checks if a string is all breaking whitespace.
 * @param {string} str The string to check.
 * @return {boolean} Whether the string is all breaking whitespace.
 */
goog.string.isBreakingWhitespace = function(str) {
  return !/[^\t\n\r ]/.test(str);
};


/**
 * Checks if a string contains all letters.
 * @param {string} str string to check.
 * @return {boolean} True if {@code str} consists entirely of letters.
 */
goog.string.isAlpha = function(str) {
  return !/[^a-zA-Z]/.test(str);
};


/**
 * Checks if a string contains only numbers.
 * @param {*} str string to check. If not a string, it will be
 *     casted to one.
 * @return {boolean} True if {@code str} is numeric.
 */
goog.string.isNumeric = function(str) {
  return !/[^0-9]/.test(str);
};


/**
 * Checks if a string contains only numbers or letters.
 * @param {string} str string to check.
 * @return {boolean} True if {@code str} is alphanumeric.
 */
goog.string.isAlphaNumeric = function(str) {
  return !/[^a-zA-Z0-9]/.test(str);
};


/**
 * Checks if a character is a space character.
 * @param {string} ch Character to check.
 * @return {boolean} True if {code ch} is a space.
 */
goog.string.isSpace = function(ch) {
  return ch == ' ';
};


/**
 * Checks if a character is a valid unicode character.
 * @param {string} ch Character to check.
 * @return {boolean} True if {code ch} is a valid unicode character.
 */
goog.string.isUnicodeChar = function(ch) {
  return ch.length == 1 && ch >= ' ' && ch <= '~' ||
         ch >= '\u0080' && ch <= '\uFFFD';
};


/**
 * Takes a string and replaces newlines with a space. Multiple lines are
 * replaced with a single space.
 * @param {string} str The string from which to strip newlines.
 * @return {string} A copy of {@code str} stripped of newlines.
 */
goog.string.stripNewlines = function(str) {
  return str.replace(/(\r\n|\r|\n)+/g, ' ');
};


/**
 * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
 * @param {string} str The string to in which to canonicalize newlines.
 * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
 */
goog.string.canonicalizeNewlines = function(str) {
  return str.replace(/(\r\n|\r|\n)/g, '\n');
};


/**
 * Normalizes whitespace in a string, replacing all whitespace chars with
 * a space.
 * @param {string} str The string in which to normalize whitespace.
 * @return {string} A copy of {@code str} with all whitespace normalized.
 */
goog.string.normalizeWhitespace = function(str) {
  return str.replace(/\xa0|\s/g, ' ');
};


/**
 * Normalizes spaces in a string, replacing all consecutive spaces and tabs
 * with a single space. Replaces non-breaking space with a space.
 * @param {string} str The string in which to normalize spaces.
 * @return {string} A copy of {@code str} with all consecutive spaces and tabs
 *    replaced with a single space.
 */
goog.string.normalizeSpaces = function(str) {
  return str.replace(/\xa0|[ \t]+/g, ' ');
};


/**
 * Removes the breaking spaces from the left and right of the string and
 * collapses the sequences of breaking spaces in the middle into single spaces.
 * The original and the result strings render the same way in HTML.
 * @param {string} str A string in which to collapse spaces.
 * @return {string} Copy of the string with normalized breaking spaces.
 */
goog.string.collapseBreakingSpaces = function(str) {
  return str.replace(/[\t\r\n ]+/g, ' ').replace(
      /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
};


/**
 * Trims white spaces to the left and right of a string.
 * @param {string} str The string to trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trim = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
};


/**
 * Trims whitespaces at the left end of a string.
 * @param {string} str The string to left trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trimLeft = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/^[\s\xa0]+/, '');
};


/**
 * Trims whitespaces at the right end of a string.
 * @param {string} str The string to right trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trimRight = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/[\s\xa0]+$/, '');
};


/**
 * A string comparator that ignores case.
 * -1 = str1 less than str2
 *  0 = str1 equals str2
 *  1 = str1 greater than str2
 *
 * @param {string} str1 The string to compare.
 * @param {string} str2 The string to compare {@code str1} to.
 * @return {number} The comparator result, as described above.
 */
goog.string.caseInsensitiveCompare = function(str1, str2) {
  var test1 = String(str1).toLowerCase();
  var test2 = String(str2).toLowerCase();

  if (test1 < test2) {
    return -1;
  } else if (test1 == test2) {
    return 0;
  } else {
    return 1;
  }
};


/**
 * Regular expression used for splitting a string into substrings of fractional
 * numbers, integers, and non-numeric characters.
 * @type {RegExp}
 * @private
 */
goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;


/**
 * String comparison function that handles numbers in a way humans might expect.
 * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
 * comparison is mostly case-insensitive, though strings that are identical
 * except for case are sorted with the upper-case strings before lower-case.
 *
 * This comparison function is significantly slower (about 500x) than either
 * the default or the case-insensitive compare. It should not be used in
 * time-critical code, but should be fast enough to sort several hundred short
 * strings (like filenames) with a reasonable delay.
 *
 * @param {string} str1 The string to compare in a numerically sensitive way.
 * @param {string} str2 The string to compare {@code str1} to.
 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
 *     0 if str1 > str2.
 */
goog.string.numerateCompare = function(str1, str2) {
  if (str1 == str2) {
    return 0;
  }
  if (!str1) {
    return -1;
  }
  if (!str2) {
    return 1;
  }

  // Using match to split the entire string ahead of time turns out to be faster
  // for most inputs than using RegExp.exec or iterating over each character.
  var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
  var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);

  var count = Math.min(tokens1.length, tokens2.length);

  for (var i = 0; i < count; i++) {
    var a = tokens1[i];
    var b = tokens2[i];

    // Compare pairs of tokens, returning if one token sorts before the other.
    if (a != b) {

      // Only if both tokens are integers is a special comparison required.
      // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
      var num1 = parseInt(a, 10);
      if (!isNaN(num1)) {
        var num2 = parseInt(b, 10);
        if (!isNaN(num2) && num1 - num2) {
          return num1 - num2;
        }
      }
      return a < b ? -1 : 1;
    }
  }

  // If one string is a substring of the other, the shorter string sorts first.
  if (tokens1.length != tokens2.length) {
    return tokens1.length - tokens2.length;
  }

  // The two strings must be equivalent except for case (perfect equality is
  // tested at the head of the function.) Revert to default ASCII-betical string
  // comparison to stablize the sort.
  return str1 < str2 ? -1 : 1;
};


/**
 * URL-encodes a string
 * @param {*} str The string to url-encode.
 * @return {string} An encoded copy of {@code str} that is safe for urls.
 *     Note that '#', ':', and other characters used to delimit portions
 *     of URLs *will* be encoded.
 */
goog.string.urlEncode = function(str) {
  return encodeURIComponent(String(str));
};


/**
 * URL-decodes the string. We need to specially handle '+'s because
 * the javascript library doesn't convert them to spaces.
 * @param {string} str The string to url decode.
 * @return {string} The decoded {@code str}.
 */
goog.string.urlDecode = function(str) {
  return decodeURIComponent(str.replace(/\+/g, ' '));
};


/**
 * Converts \n to <br>s or <br />s.
 * @param {string} str The string in which to convert newlines.
 * @param {boolean=} opt_xml Whether to use XML compatible tags.
 * @return {string} A copy of {@code str} with converted newlines.
 */
goog.string.newLineToBr = function(str, opt_xml) {
  return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
};


/**
 * Escape double quote '"' characters in addition to '&', '<', and '>' so that a
 * string can be included in an HTML tag attribute value within double quotes.
 *
 * It should be noted that > doesn't need to be escaped for the HTML or XML to
 * be valid, but it has been decided to escape it for consistency with other
 * implementations.
 *
 * NOTE(user):
 * HtmlEscape is often called during the generation of large blocks of HTML.
 * Using statics for the regular expressions and strings is an optimization
 * that can more than half the amount of time IE spends in this function for
 * large apps, since strings and regexes both contribute to GC allocations.
 *
 * Testing for the presence of a character before escaping increases the number
 * of function calls, but actually provides a speed increase for the average
 * case -- since the average case often doesn't require the escaping of all 4
 * characters and indexOf() is much cheaper than replace().
 * The worst case does suffer slightly from the additional calls, therefore the
 * opt_isLikelyToContainHtmlChars option has been included for situations
 * where all 4 HTML entities are very likely to be present and need escaping.
 *
 * Some benchmarks (times tended to fluctuate +-0.05ms):
 *                                     FireFox                     IE6
 * (no chars / average (mix of cases) / all 4 chars)
 * no checks                     0.13 / 0.22 / 0.22         0.23 / 0.53 / 0.80
 * indexOf                       0.08 / 0.17 / 0.26         0.22 / 0.54 / 0.84
 * indexOf + re test             0.07 / 0.17 / 0.28         0.19 / 0.50 / 0.85
 *
 * An additional advantage of checking if replace actually needs to be called
 * is a reduction in the number of object allocations, so as the size of the
 * application grows the difference between the various methods would increase.
 *
 * @param {string} str string to be escaped.
 * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
 *     if the character needs replacing - use this option if you expect each of
 *     the characters to appear often. Leave false if you expect few html
 *     characters to occur in your strings, such as if you are escaping HTML.
 * @return {string} An escaped copy of {@code str}.
 */
goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {

  if (opt_isLikelyToContainHtmlChars) {
    return str.replace(goog.string.amperRe_, '&amp;')
          .replace(goog.string.ltRe_, '&lt;')
          .replace(goog.string.gtRe_, '&gt;')
          .replace(goog.string.quotRe_, '&quot;');

  } else {
    // quick test helps in the case when there are no chars to replace, in
    // worst case this makes barely a difference to the time taken
    if (!goog.string.allRe_.test(str)) return str;

    // str.indexOf is faster than regex.test in this case
    if (str.indexOf('&') != -1) {
      str = str.replace(goog.string.amperRe_, '&amp;');
    }
    if (str.indexOf('<') != -1) {
      str = str.replace(goog.string.ltRe_, '&lt;');
    }
    if (str.indexOf('>') != -1) {
      str = str.replace(goog.string.gtRe_, '&gt;');
    }
    if (str.indexOf('"') != -1) {
      str = str.replace(goog.string.quotRe_, '&quot;');
    }
    return str;
  }
};


/**
 * Regular expression that matches an ampersand, for use in escaping.
 * @type {RegExp}
 * @private
 */
goog.string.amperRe_ = /&/g;


/**
 * Regular expression that matches a less than sign, for use in escaping.
 * @type {RegExp}
 * @private
 */
goog.string.ltRe_ = /</g;


/**
 * Regular expression that matches a greater than sign, for use in escaping.
 * @type {RegExp}
 * @private
 */
goog.string.gtRe_ = />/g;


/**
 * Regular expression that matches a double quote, for use in escaping.
 * @type {RegExp}
 * @private
 */
goog.string.quotRe_ = /\"/g;


/**
 * Regular expression that matches any character that needs to be escaped.
 * @type {RegExp}
 * @private
 */
goog.string.allRe_ = /[&<>\"]/;


/**
 * Unescapes an HTML string.
 *
 * @param {string} str The string to unescape.
 * @return {string} An unescaped copy of {@code str}.
 */
goog.string.unescapeEntities = function(str) {
  if (goog.string.contains(str, '&')) {
    // We are careful not to use a DOM if we do not have one. We use the []
    // notation so that the JSCompiler will not complain about these objects and
    // fields in the case where we have no DOM.
    if ('document' in goog.global) {
      return goog.string.unescapeEntitiesUsingDom_(str);
    } else {
      // Fall back on pure XML entities
      return goog.string.unescapePureXmlEntities_(str);
    }
  }
  return str;
};


/**
 * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
 * entities. This function is XSS-safe and whitespace-preserving.
 * @private
 * @param {string} str The string to unescape.
 * @return {string} The unescaped {@code str} string.
 */
goog.string.unescapeEntitiesUsingDom_ = function(str) {
  var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
  var div = document.createElement('div');
  // Match as many valid entity characters as possible. If the actual entity
  // happens to be shorter, it will still work as innerHTML will return the
  // trailing characters unchanged. Since the entity characters do not include
  // open angle bracket, there is no chance of XSS from the innerHTML use.
  // Since no whitespace is passed to innerHTML, whitespace is preserved.
  return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
    // Check for cached entity.
    var value = seen[s];
    if (value) {
      return value;
    }
    // Check for numeric entity.
    if (entity.charAt(0) == '#') {
      // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
      var n = Number('0' + entity.substr(1));
      if (!isNaN(n)) {
        value = String.fromCharCode(n);
      }
    }
    // Fall back to innerHTML otherwise.
    if (!value) {
      // Append a non-entity character to avoid a bug in Webkit that parses
      // an invalid entity at the end of innerHTML text as the empty string.
      div.innerHTML = s + ' ';
      // Then remove the trailing character from the result.
      value = div.firstChild.nodeValue.slice(0, -1);
    }
    // Cache and return.
    return seen[s] = value;
  });
};


/**
 * Unescapes XML entities.
 * @private
 * @param {string} str The string to unescape.
 * @return {string} An unescaped copy of {@code str}.
 */
goog.string.unescapePureXmlEntities_ = function(str) {
  return str.replace(/&([^;]+);/g, function(s, entity) {
    switch (entity) {
      case 'amp':
        return '&';
      case 'lt':
        return '<';
      case 'gt':
        return '>';
      case 'quot':
        return '"';
      default:
        if (entity.charAt(0) == '#') {
          // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
          var n = Number('0' + entity.substr(1));
          if (!isNaN(n)) {
            return String.fromCharCode(n);
          }
        }
        // For invalid entities we just return the entity
        return s;
    }
  });
};


/**
 * Regular expression that matches an HTML entity.
 * See also HTML5: Tokenization / Tokenizing character references.
 * @private
 * @type {!RegExp}
 */
goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;


/**
 * Do escaping of whitespace to preserve spatial formatting. We use character
 * entity #160 to make it safer for xml.
 * @param {string} str The string in which to escape whitespace.
 * @param {boolean=} opt_xml Whether to use XML compatible tags.
 * @return {string} An escaped copy of {@code str}.
 */
goog.string.whitespaceEscape = function(str, opt_xml) {
  return goog.string.newLineToBr(str.replace(/  /g, ' &#160;'), opt_xml);
};


/**
 * Strip quote characters around a string.  The second argument is a string of
 * characters to treat as quotes.  This can be a single character or a string of
 * multiple character and in that case each of those are treated as possible
 * quote characters. For example:
 *
 * <pre>
 * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
 * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
 * </pre>
 *
 * @param {string} str The string to strip.
 * @param {string} quoteChars The quote characters to strip.
 * @return {string} A copy of {@code str} without the quotes.
 */
goog.string.stripQuotes = function(str, quoteChars) {
  var length = quoteChars.length;
  for (var i = 0; i < length; i++) {
    var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
    if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
      return str.substring(1, str.length - 1);
    }
  }
  return str;
};


/**
 * Truncates a string to a certain length and adds '...' if necessary.  The
 * length also accounts for the ellipsis, so a maximum length of 10 and a string
 * 'Hello World!' produces 'Hello W...'.
 * @param {string} str The string to truncate.
 * @param {number} chars Max number of characters.
 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
 *     characters from being cut off in the middle.
 * @return {string} The truncated {@code str} string.
 */
goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
  if (opt_protectEscapedCharacters) {
    str = goog.string.unescapeEntities(str);
  }

  if (str.length > chars) {
    str = str.substring(0, chars - 3) + '...';
  }

  if (opt_protectEscapedCharacters) {
    str = goog.string.htmlEscape(str);
  }

  return str;
};


/**
 * Truncate a string in the middle, adding "..." if necessary,
 * and favoring the beginning of the string.
 * @param {string} str The string to truncate the middle of.
 * @param {number} chars Max number of characters.
 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
 *     characters from being cutoff in the middle.
 * @param {number=} opt_trailingChars Optional number of trailing characters to
 *     leave at the end of the string, instead of truncating as close to the
 *     middle as possible.
 * @return {string} A truncated copy of {@code str}.
 */
goog.string.truncateMiddle = function(str, chars,
    opt_protectEscapedCharacters, opt_trailingChars) {
  if (opt_protectEscapedCharacters) {
    str = goog.string.unescapeEntities(str);
  }

  if (opt_trailingChars && str.length > chars) {
    if (opt_trailingChars > chars) {
      opt_trailingChars = chars;
    }
    var endPoint = str.length - opt_trailingChars;
    var startPoint = chars - opt_trailingChars;
    str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
  } else if (str.length > chars) {
    // Favor the beginning of the string:
    var half = Math.floor(chars / 2);
    var endPos = str.length - half;
    half += chars % 2;
    str = str.substring(0, half) + '...' + str.substring(endPos);
  }

  if (opt_protectEscapedCharacters) {
    str = goog.string.htmlEscape(str);
  }

  return str;
};


/**
 * Special chars that need to be escaped for goog.string.quote.
 * @private
 * @type {Object}
 */
goog.string.specialEscapeChars_ = {
  '\0': '\\0',
  '\b': '\\b',
  '\f': '\\f',
  '\n': '\\n',
  '\r': '\\r',
  '\t': '\\t',
  '\x0B': '\\x0B', // '\v' is not supported in JScript
  '"': '\\"',
  '\\': '\\\\'
};


/**
 * Character mappings used internally for goog.string.escapeChar.
 * @private
 * @type {Object}
 */
goog.string.jsEscapeCache_ = {
  '\'': '\\\''
};


/**
 * Encloses a string in double quotes and escapes characters so that the
 * string is a valid JS string.
 * @param {string} s The string to quote.
 * @return {string} A copy of {@code s} surrounded by double quotes.
 */
goog.string.quote = function(s) {
  s = String(s);
  if (s.quote) {
    return s.quote();
  } else {
    var sb = ['"'];
    for (var i = 0; i < s.length; i++) {
      var ch = s.charAt(i);
      var cc = ch.charCodeAt(0);
      sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
          ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
    }
    sb.push('"');
    return sb.join('');
  }
};


/**
 * Takes a string and returns the escaped string for that character.
 * @param {string} str The string to escape.
 * @return {string} An escaped string representing {@code str}.
 */
goog.string.escapeString = function(str) {
  var sb = [];
  for (var i = 0; i < str.length; i++) {
    sb[i] = goog.string.escapeChar(str.charAt(i));
  }
  return sb.join('');
};


/**
 * Takes a character and returns the escaped string for that character. For
 * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
 * @param {string} c The character to escape.
 * @return {string} An escaped string representing {@code c}.
 */
goog.string.escapeChar = function(c) {
  if (c in goog.string.jsEscapeCache_) {
    return goog.string.jsEscapeCache_[c];
  }

  if (c in goog.string.specialEscapeChars_) {
    return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
  }

  var rv = c;
  var cc = c.charCodeAt(0);
  if (cc > 31 && cc < 127) {
    rv = c;
  } else {
    // tab is 9 but handled above
    if (cc < 256) {
      rv = '\\x';
      if (cc < 16 || cc > 256) {
        rv += '0';
      }
    } else {
      rv = '\\u';
      if (cc < 4096) { // \u1000
        rv += '0';
      }
    }
    rv += cc.toString(16).toUpperCase();
  }

  return goog.string.jsEscapeCache_[c] = rv;
};


/**
 * Takes a string and creates a map (Object) in which the keys are the
 * characters in the string. The value for the key is set to true. You can
 * then use goog.object.map or goog.array.map to change the values.
 * @param {string} s The string to build the map from.
 * @return {Object} The map of characters used.
 */
// TODO(arv): It seems like we should have a generic goog.array.toMap. But do
//            we want a dependency on goog.array in goog.string?
goog.string.toMap = function(s) {
  var rv = {};
  for (var i = 0; i < s.length; i++) {
    rv[s.charAt(i)] = true;
  }
  return rv;
};


/**
 * Checks whether a string contains a given substring.
 * @param {string} s The string to test.
 * @param {string} ss The substring to test for.
 * @return {boolean} True if {@code s} contains {@code ss}.
 */
goog.string.contains = function(s, ss) {
  return s.indexOf(ss) != -1;
};


/**
 * Returns the non-overlapping occurrences of ss in s.
 * If either s or ss evalutes to false, then returns zero.
 * @param {string} s The string to look in.
 * @param {string} ss The string to look for.
 * @return {number} Number of occurrences of ss in s.
 */
goog.string.countOf = function(s, ss) {
  return s && ss ? s.split(ss).length - 1 : 0;
};


/**
 * Removes a substring of a specified length at a specific
 * index in a string.
 * @param {string} s The base string from which to remove.
 * @param {number} index The index at which to remove the substring.
 * @param {number} stringLength The length of the substring to remove.
 * @return {string} A copy of {@code s} with the substring removed or the full
 *     string if nothing is removed or the input is invalid.
 */
goog.string.removeAt = function(s, index, stringLength) {
  var resultStr = s;
  // If the index is greater or equal to 0 then remove substring
  if (index >= 0 && index < s.length && stringLength > 0) {
    resultStr = s.substr(0, index) +
        s.substr(index + stringLength, s.length - index - stringLength);
  }
  return resultStr;
};


/**
 *  Removes the first occurrence of a substring from a string.
 *  @param {string} s The base string from which to remove.
 *  @param {string} ss The string to remove.
 *  @return {string} A copy of {@code s} with {@code ss} removed or the full
 *      string if nothing is removed.
 */
goog.string.remove = function(s, ss) {
  var re = new RegExp(goog.string.regExpEscape(ss), '');
  return s.replace(re, '');
};


/**
 *  Removes all occurrences of a substring from a string.
 *  @param {string} s The base string from which to remove.
 *  @param {string} ss The string to remove.
 *  @return {string} A copy of {@code s} with {@code ss} removed or the full
 *      string if nothing is removed.
 */
goog.string.removeAll = function(s, ss) {
  var re = new RegExp(goog.string.regExpEscape(ss), 'g');
  return s.replace(re, '');
};


/**
 * Escapes characters in the string that are not safe to use in a RegExp.
 * @param {*} s The string to escape. If not a string, it will be casted
 *     to one.
 * @return {string} A RegExp safe, escaped copy of {@code s}.
 */
goog.string.regExpEscape = function(s) {
  return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
      replace(/\x08/g, '\\x08');
};


/**
 * Repeats a string n times.
 * @param {string} string The string to repeat.
 * @param {number} length The number of times to repeat.
 * @return {string} A string containing {@code length} repetitions of
 *     {@code string}.
 */
goog.string.repeat = function(string, length) {
  return new Array(length + 1).join(string);
};


/**
 * Pads number to given length and optionally rounds it to a given precision.
 * For example:
 * <pre>padNumber(1.25, 2, 3) -> '01.250'
 * padNumber(1.25, 2) -> '01.25'
 * padNumber(1.25, 2, 1) -> '01.3'
 * padNumber(1.25, 0) -> '1.25'</pre>
 *
 * @param {number} num The number to pad.
 * @param {number} length The desired length.
 * @param {number=} opt_precision The desired precision.
 * @return {string} {@code num} as a string with the given options.
 */
goog.string.padNumber = function(num, length, opt_precision) {
  var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
  var index = s.indexOf('.');
  if (index == -1) {
    index = s.length;
  }
  return goog.string.repeat('0', Math.max(0, length - index)) + s;
};


/**
 * Returns a string representation of the given object, with
 * null and undefined being returned as the empty string.
 *
 * @param {*} obj The object to convert.
 * @return {string} A string representation of the {@code obj}.
 */
goog.string.makeSafe = function(obj) {
  return obj == null ? '' : String(obj);
};


/**
 * Concatenates string expressions. This is useful
 * since some browsers are very inefficient when it comes to using plus to
 * concat strings. Be careful when using null and undefined here since
 * these will not be included in the result. If you need to represent these
 * be sure to cast the argument to a String first.
 * For example:
 * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
 * buildString(null, undefined) -> ''
 * </pre>
 * @param {...*} var_args A list of strings to concatenate. If not a string,
 *     it will be casted to one.
 * @return {string} The concatenation of {@code var_args}.
 */
goog.string.buildString = function(var_args) {
  return Array.prototype.join.call(arguments, '');
};


/**
 * Returns a string with at least 64-bits of randomness.
 *
 * Doesn't trust Javascript's random function entirely. Uses a combination of
 * random and current timestamp, and then encodes the string in base-36 to
 * make it shorter.
 *
 * @return {string} A random string, e.g. sn1s7vb4gcic.
 */
goog.string.getRandomString = function() {
  var x = 2147483648;
  return Math.floor(Math.random() * x).toString(36) +
         Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
};


/**
 * Compares two version numbers.
 *
 * @param {string|number} version1 Version of first item.
 * @param {string|number} version2 Version of second item.
 *
 * @return {number}  1 if {@code version1} is higher.
 *                   0 if arguments are equal.
 *                  -1 if {@code version2} is higher.
 */
goog.string.compareVersions = function(version1, version2) {
  var order = 0;
  // Trim leading and trailing whitespace and split the versions into
  // subversions.
  var v1Subs = goog.string.trim(String(version1)).split('.');
  var v2Subs = goog.string.trim(String(version2)).split('.');
  var subCount = Math.max(v1Subs.length, v2Subs.length);

  // Iterate over the subversions, as long as they appear to be equivalent.
  for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
    var v1Sub = v1Subs[subIdx] || '';
    var v2Sub = v2Subs[subIdx] || '';

    // Split the subversions into pairs of numbers and qualifiers (like 'b').
    // Two different RegExp objects are needed because they are both using
    // the 'g' flag.
    var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
    var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
    do {
      var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
      var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
      // Break if there are no more matches.
      if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
        break;
      }

      // Parse the numeric part of the subversion. A missing number is
      // equivalent to 0.
      var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
      var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);

      // Compare the subversion components. The number has the highest
      // precedence. Next, if the numbers are equal, a subversion without any
      // qualifier is always higher than a subversion with any qualifier. Next,
      // the qualifiers are compared as strings.
      order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
          goog.string.compareElements_(v1Comp[2].length == 0,
              v2Comp[2].length == 0) ||
          goog.string.compareElements_(v1Comp[2], v2Comp[2]);
      // Stop as soon as an inequality is discovered.
    } while (order == 0);
  }

  return order;
};


/**
 * Compares elements of a version number.
 *
 * @param {string|number|boolean} left An element from a version number.
 * @param {string|number|boolean} right An element from a version number.
 *
 * @return {number}  1 if {@code left} is higher.
 *                   0 if arguments are equal.
 *                  -1 if {@code right} is higher.
 * @private
 */
goog.string.compareElements_ = function(left, right) {
  if (left < right) {
    return -1;
  } else if (left > right) {
    return 1;
  }
  return 0;
};


/**
 * Maximum value of #goog.string.hashCode, exclusive. 2^32.
 * @type {number}
 * @private
 */
goog.string.HASHCODE_MAX_ = 0x100000000;


/**
 * String hash function similar to java.lang.String.hashCode().
 * The hash code for a string is computed as
 * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
 * where s[i] is the ith character of the string and n is the length of
 * the string. We mod the result to make it between 0 (inclusive) and 2^32
 * (exclusive).
 * @param {string} str A string.
 * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
 *  (exclusive). The empty string returns 0.
 */
goog.string.hashCode = function(str) {
  var result = 0;
  for (var i = 0; i < str.length; ++i) {
    result = 31 * result + str.charCodeAt(i);
    // Normalize to 4 byte range, 0 ... 2^32.
    result %= goog.string.HASHCODE_MAX_;
  }
  return result;
};


/**
 * The most recent unique ID. |0 is equivalent to Math.floor in this case.
 * @type {number}
 * @private
 */
goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;


/**
 * Generates and returns a string which is unique in the current document.
 * This is useful, for example, to create unique IDs for DOM elements.
 * @return {string} A unique id.
 */
goog.string.createUniqueString = function() {
  return 'goog_' + goog.string.uniqueStringCounter_++;
};


/**
 * Converts the supplied string to a number, which may be Ininity or NaN.
 * This function strips whitespace: (toNumber(' 123') === 123)
 * This function accepts scientific notation: (toNumber('1e1') === 10)
 *
 * This is better than Javascript's built-in conversions because, sadly:
 *     (Number(' ') === 0) and (parseFloat('123a') === 123)
 *
 * @param {string} str The string to convert.
 * @return {number} The number the supplied string represents, or NaN.
 */
goog.string.toNumber = function(str) {
  var num = Number(str);
  if (num == 0 && goog.string.isEmpty(str)) {
    return NaN;
  }
  return num;
};


/**
 * Returns whether the given string is lower camel case (e.g. "isFooBar").
 *
 * Note that this assumes the string is entirely letters.
 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
 *
 * @param {string} str String to test.
 * @return {boolean} Whether the string is lower camel case.
 */
goog.string.isLowerCamelCase = function(str) {
  return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
};


/**
 * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
 *
 * Note that this assumes the string is entirely letters.
 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
 *
 * @param {string} str String to test.
 * @return {boolean} Whether the string is upper camel case.
 */
goog.string.isUpperCamelCase = function(str) {
  return /^([A-Z][a-z]*)+$/.test(str);
};


/**
 * Converts a string from selector-case to camelCase (e.g. from
 * "multi-part-string" to "multiPartString"), useful for converting
 * CSS selectors and HTML dataset keys to their equivalent JS properties.
 * @param {string} str The string in selector-case form.
 * @return {string} The string in camelCase form.
 */
goog.string.toCamelCase = function(str) {
  return String(str).replace(/\-([a-z])/g, function(all, match) {
    return match.toUpperCase();
  });
};


/**
 * Converts a string from camelCase to selector-case (e.g. from
 * "multiPartString" to "multi-part-string"), useful for converting JS
 * style and dataset properties to equivalent CSS selectors and HTML keys.
 * @param {string} str The string in camelCase form.
 * @return {string} The string in selector-case form.
 */
goog.string.toSelectorCase = function(str) {
  return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
};


/**
 * Converts a string into TitleCase. First character of the string is always
 * capitalized in addition to the first letter of every subsequent word.
 * Words are delimited by one or more whitespaces by default. Custom delimiters
 * can optionally be specified to replace the default, which doesn't preserve
 * whitespace delimiters and instead must be explicitly included if needed.
 *
 * Default delimiter => " ":
 *    goog.string.toTitleCase('oneTwoThree')    => 'OneTwoThree'
 *    goog.string.toTitleCase('one two three')  => 'One Two Three'
 *    goog.string.toTitleCase('  one   two   ') => '  One   Two   '
 *    goog.string.toTitleCase('one_two_three')  => 'One_two_three'
 *    goog.string.toTitleCase('one-two-three')  => 'One-two-three'
 *
 * Custom delimiter => "_-.":
 *    goog.string.toTitleCase('oneTwoThree', '_-.')       => 'OneTwoThree'
 *    goog.string.toTitleCase('one two three', '_-.')     => 'One two three'
 *    goog.string.toTitleCase('  one   two   ', '_-.')    => '  one   two   '
 *    goog.string.toTitleCase('one_two_three', '_-.')     => 'One_Two_Three'
 *    goog.string.toTitleCase('one-two-three', '_-.')     => 'One-Two-Three'
 *    goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
 *    goog.string.toTitleCase('one. two. three', '_-.')   => 'One. two. three'
 *    goog.string.toTitleCase('one-two.three', '_-.')     => 'One-Two.Three'
 *
 * @param {string} str String value in camelCase form.
 * @param {string=} opt_delimiters Custom delimiter character set used to
 *      distinguish words in the string value. Each character represents a
 *      single delimiter. When provided, default whitespace delimiter is
 *      overridden and must be explicitly included if needed.
 * @return {string} String value in TitleCase form.
 */
goog.string.toTitleCase = function(str, opt_delimiters) {
  var delimiters = goog.isString(opt_delimiters) ?
      goog.string.regExpEscape(opt_delimiters) : '\\s';

  // For IE8, we need to prevent using an empty character set. Otherwise,
  // incorrect matching will occur.
  delimiters = delimiters ? '|[' + delimiters + ']+' : '';

  var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
  return str.replace(regexp, function(all, p1, p2) {
    return p1 + p2.toUpperCase();
  });
};


/**
 * Parse a string in decimal or hexidecimal ('0xFFFF') form.
 *
 * To parse a particular radix, please use parseInt(string, radix) directly. See
 * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
 *
 * This is a wrapper for the built-in parseInt function that will only parse
 * numbers as base 10 or base 16.  Some JS implementations assume strings
 * starting with "0" are intended to be octal. ES3 allowed but discouraged
 * this behavior. ES5 forbids it.  This function emulates the ES5 behavior.
 *
 * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
 *
 * @param {string|number|null|undefined} value The value to be parsed.
 * @return {number} The number, parsed. If the string failed to parse, this
 *     will be NaN.
 */
goog.string.parseInt = function(value) {
  // Force finite numbers to strings.
  if (isFinite(value)) {
    value = String(value);
  }

  if (goog.isString(value)) {
    // If the string starts with '0x' or '-0x', parse as hex.
    return /^\s*-?0x/i.test(value) ?
        parseInt(value, 16) : parseInt(value, 10);
  }

  return NaN;
};

// Input 3
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities to check the preconditions, postconditions and
 * invariants runtime.
 *
 * Methods in this package should be given special treatment by the compiler
 * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
 * will restrict <code>foo</code> to a truthy value.
 *
 * The compiler has an option to disable asserts. So code like:
 * <code>
 * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
 * </code>
 * will be transformed into:
 * <code>
 * var x = foo();
 * </code>
 * The compiler will leave in foo() (because its return value is used),
 * but it will remove bar() because it assumes it does not have side-effects.
 *
 */

goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');

goog.require('goog.debug.Error');
goog.require('goog.string');


/**
 * @define {boolean} Whether to strip out asserts or to leave them in.
 */
goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);



/**
 * Error object for failed assertions.
 * @param {string} messagePattern The pattern that was used to form message.
 * @param {!Array.<*>} messageArgs The items to substitute into the pattern.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.asserts.AssertionError = function(messagePattern, messageArgs) {
  messageArgs.unshift(messagePattern);
  goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
  // Remove the messagePattern afterwards to avoid permenantly modifying the
  // passed in array.
  messageArgs.shift();

  /**
   * The message pattern used to format the error message. Error handlers can
   * use this to uniquely identify the assertion.
   * @type {string}
   */
  this.messagePattern = messagePattern;
};
goog.inherits(goog.asserts.AssertionError, goog.debug.Error);


/** @override */
goog.asserts.AssertionError.prototype.name = 'AssertionError';


/**
 * Throws an exception with the given message and "Assertion failed" prefixed
 * onto it.
 * @param {string} defaultMessage The message to use if givenMessage is empty.
 * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage.
 * @param {string|undefined} givenMessage Message supplied by the caller.
 * @param {Array.<*>} givenArgs The substitution arguments for givenMessage.
 * @throws {goog.asserts.AssertionError} When the value is not a number.
 * @private
 */
goog.asserts.doAssertFailure_ =
    function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
  var message = 'Assertion failed';
  if (givenMessage) {
    message += ': ' + givenMessage;
    var args = givenArgs;
  } else if (defaultMessage) {
    message += ': ' + defaultMessage;
    args = defaultArgs;
  }
  // The '' + works around an Opera 10 bug in the unit tests. Without it,
  // a stack trace is added to var message above. With this, a stack trace is
  // not added until this line (it causes the extra garbage to be added after
  // the assertion message instead of in the middle of it).
  throw new goog.asserts.AssertionError('' + message, args || []);
};


/**
 * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
 * true.
 * @param {*} condition The condition to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {*} The value of the condition.
 * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
 */
goog.asserts.assert = function(condition, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !condition) {
    goog.asserts.doAssertFailure_('', null, opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return condition;
};


/**
 * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
 * when we want to add a check in the unreachable area like switch-case
 * statement:
 *
 * <pre>
 *  switch(type) {
 *    case FOO: doSomething(); break;
 *    case BAR: doSomethingElse(); break;
 *    default: goog.assert.fail('Unrecognized type: ' + type);
 *      // We have only 2 types - "default:" section is unreachable code.
 *  }
 * </pre>
 *
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @throws {goog.asserts.AssertionError} Failure.
 */
goog.asserts.fail = function(opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS) {
    throw new goog.asserts.AssertionError(
        'Failure' + (opt_message ? ': ' + opt_message : ''),
        Array.prototype.slice.call(arguments, 1));
  }
};


/**
 * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {number} The value, guaranteed to be a number when asserts enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a number.
 */
goog.asserts.assertNumber = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
    goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
        [goog.typeOf(value), value], opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {number} */ (value);
};


/**
 * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {string} The value, guaranteed to be a string when asserts enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a string.
 */
goog.asserts.assertString = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
    goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
        [goog.typeOf(value), value], opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {string} */ (value);
};


/**
 * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Function} The value, guaranteed to be a function when asserts
 *     enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a function.
 */
goog.asserts.assertFunction = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
    goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
        [goog.typeOf(value), value], opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Function} */ (value);
};


/**
 * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Object} The value, guaranteed to be a non-null object.
 * @throws {goog.asserts.AssertionError} When the value is not an object.
 */
goog.asserts.assertObject = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
    goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
        [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Object} */ (value);
};


/**
 * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Array} The value, guaranteed to be a non-null array.
 * @throws {goog.asserts.AssertionError} When the value is not an array.
 */
goog.asserts.assertArray = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
    goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
        [goog.typeOf(value), value], opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Array} */ (value);
};


/**
 * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {boolean} The value, guaranteed to be a boolean when asserts are
 *     enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a boolean.
 */
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
    goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
        [goog.typeOf(value), value], opt_message,
        Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {boolean} */ (value);
};


/**
 * Checks if the value is an instance of the user-defined type if
 * goog.asserts.ENABLE_ASSERTS is true.
 *
 * The compiler may tighten the type returned by this function.
 *
 * @param {*} value The value to check.
 * @param {!Function} type A user-defined constructor.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @throws {goog.asserts.AssertionError} When the value is not an instance of
 *     type.
 * @return {!Object}
 */
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
    goog.asserts.doAssertFailure_('instanceof check failed.', null,
        opt_message, Array.prototype.slice.call(arguments, 3));
  }
  return /** @type {!Object} */(value);
};


// Input 4
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for manipulating arrays.
 *
 */


goog.provide('goog.array');
goog.provide('goog.array.ArrayLike');

goog.require('goog.asserts');


/**
 * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
 * rely on Array.prototype functions, if available.
 *
 * The Array.prototype functions can be defined by external libraries like
 * Prototype and setting this flag to false forces closure to use its own
 * goog.array implementation.
 *
 * If your javascript can be loaded by a third party site and you are wary about
 * relying on the prototype functions, specify
 * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
 *
 * Setting goog.TRUSTED_SITE to false will automatically set
 * NATIVE_ARRAY_PROTOTYPES to false.
 */
goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);


/**
 * @typedef {Array|NodeList|Arguments|{length: number}}
 */
goog.array.ArrayLike;


/**
 * Returns the last element in an array without removing it.
 * @param {goog.array.ArrayLike} array The array.
 * @return {*} Last item in array.
 */
goog.array.peek = function(array) {
  return array[array.length - 1];
};


/**
 * Reference to the original {@code Array.prototype}.
 * @private
 */
goog.array.ARRAY_PROTOTYPE_ = Array.prototype;


// NOTE(arv): Since most of the array functions are generic it allows you to
// pass an array-like object. Strings have a length and are considered array-
// like. However, the 'in' operator does not work on strings so we cannot just
// use the array path even if the browser supports indexing into strings. We
// therefore end up splitting the string.


/**
 * Returns the index of the first element of an array with a specified
 * value, or -1 if the element is not present in the array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
 *
 * @param {goog.array.ArrayLike} arr The array to be searched.
 * @param {*} obj The object for which we are searching.
 * @param {number=} opt_fromIndex The index at which to start the search. If
 *     omitted the search starts at index 0.
 * @return {number} The index of the first matching array element.
 */
goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
                     goog.array.ARRAY_PROTOTYPE_.indexOf ?
    function(arr, obj, opt_fromIndex) {
      goog.asserts.assert(arr.length != null);

      return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
    } :
    function(arr, obj, opt_fromIndex) {
      var fromIndex = opt_fromIndex == null ?
          0 : (opt_fromIndex < 0 ?
               Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);

      if (goog.isString(arr)) {
        // Array.prototype.indexOf uses === so only strings should be found.
        if (!goog.isString(obj) || obj.length != 1) {
          return -1;
        }
        return arr.indexOf(obj, fromIndex);
      }

      for (var i = fromIndex; i < arr.length; i++) {
        if (i in arr && arr[i] === obj)
          return i;
      }
      return -1;
    };


/**
 * Returns the index of the last element of an array with a specified value, or
 * -1 if the element is not present in the array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
 *
 * @param {goog.array.ArrayLike} arr The array to be searched.
 * @param {*} obj The object for which we are searching.
 * @param {?number=} opt_fromIndex The index at which to start the search. If
 *     omitted the search starts at the end of the array.
 * @return {number} The index of the last matching array element.
 */
goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
                         goog.array.ARRAY_PROTOTYPE_.lastIndexOf ?
    function(arr, obj, opt_fromIndex) {
      goog.asserts.assert(arr.length != null);

      // Firefox treats undefined and null as 0 in the fromIndex argument which
      // leads it to always return -1
      var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
      return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
    } :
    function(arr, obj, opt_fromIndex) {
      var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;

      if (fromIndex < 0) {
        fromIndex = Math.max(0, arr.length + fromIndex);
      }

      if (goog.isString(arr)) {
        // Array.prototype.lastIndexOf uses === so only strings should be found.
        if (!goog.isString(obj) || obj.length != 1) {
          return -1;
        }
        return arr.lastIndexOf(obj, fromIndex);
      }

      for (var i = fromIndex; i >= 0; i--) {
        if (i in arr && arr[i] === obj)
          return i;
      }
      return -1;
    };


/**
 * Calls a function for each element in an array. Skips holes in the array.
 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
 *     which to iterate.
 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
 *     element. This function takes 3 arguments (the element, the index and the
 *     array). The return value is ignored.
 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
 * @template T,S
 */
goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
                     goog.array.ARRAY_PROTOTYPE_.forEach ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          f.call(opt_obj, arr2[i], i, arr);
        }
      }
    };


/**
 * Calls a function for each element in an array, starting from the last
 * element rather than the first.
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
 *     element. This function
 *     takes 3 arguments (the element, the index and the array). The return
 *     value is ignored.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @template T,S
 */
goog.array.forEachRight = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = l - 1; i >= 0; --i) {
    if (i in arr2) {
      f.call(opt_obj, arr2[i], i, arr);
    }
  }
};


/**
 * Calls a function for each element in an array, and if the function returns
 * true adds the element to a new array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?):boolean} f The function to call for
 *     every element. This function
 *     takes 3 arguments (the element, the index and the array) and must
 *     return a Boolean. If the return value is true the element is added to the
 *     result array. If it is false the element is not included.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {!Array} a new array in which only elements that passed the test are
 *     present.
 * @template T,S
 */
goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
                    goog.array.ARRAY_PROTOTYPE_.filter ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var res = [];
      var resLength = 0;
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          var val = arr2[i];  // in case f mutates arr2
          if (f.call(opt_obj, val, i, arr)) {
            res[resLength++] = val;
          }
        }
      }
      return res;
    };


/**
 * Calls a function for each element in an array and inserts the result into a
 * new array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?):?} f The function to call for every
 *     element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return something. The result will be inserted into a new array.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {!Array} a new array with the results from f.
 * @template T,S
 */
goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
                 goog.array.ARRAY_PROTOTYPE_.map ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var res = new Array(l);
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          res[i] = f.call(opt_obj, arr2[i], i, arr);
        }
      }
      return res;
    };


/**
 * Passes every element of an array into a function and accumulates the result.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
 *
 * For example:
 * var a = [1, 2, 3, 4];
 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
 * returns 10
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
 *     every element. This function
 *     takes 4 arguments (the function's previous result or the initial value,
 *     the value of the current array element, the current array index, and the
 *     array itself)
 *     function(previousValue, currentValue, index, array).
 * @param {?} val The initial value to pass into the function on the first call.
 * @param {S=} opt_obj  The object to be used as the value of 'this'
 *     within f.
 * @return {R} Result of evaluating f repeatedly across the values of the array.
 * @template T,S,R
 */
goog.array.reduce = function(arr, f, val, opt_obj) {
  if (arr.reduce) {
    if (opt_obj) {
      return arr.reduce(goog.bind(f, opt_obj), val);
    } else {
      return arr.reduce(f, val);
    }
  }
  var rval = val;
  goog.array.forEach(arr, function(val, index) {
    rval = f.call(opt_obj, rval, val, index, arr);
  });
  return rval;
};


/**
 * Passes every element of an array into a function and accumulates the result,
 * starting from the last element and working towards the first.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
 *
 * For example:
 * var a = ['a', 'b', 'c'];
 * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
 * returns 'cba'
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
 *     every element. This function
 *     takes 4 arguments (the function's previous result or the initial value,
 *     the value of the current array element, the current array index, and the
 *     array itself)
 *     function(previousValue, currentValue, index, array).
 * @param {?} val The initial value to pass into the function on the first call.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {R} Object returned as a result of evaluating f repeatedly across the
 *     values of the array.
 * @template T,S,R
 */
goog.array.reduceRight = function(arr, f, val, opt_obj) {
  if (arr.reduceRight) {
    if (opt_obj) {
      return arr.reduceRight(goog.bind(f, opt_obj), val);
    } else {
      return arr.reduceRight(f, val);
    }
  }
  var rval = val;
  goog.array.forEachRight(arr, function(val, index) {
    rval = f.call(opt_obj, rval, val, index, arr);
  });
  return rval;
};


/**
 * Calls f for each element of an array. If any call returns true, some()
 * returns true (without checking the remaining elements). If all calls
 * return false, some() returns false.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj  The object to be used as the value of 'this'
 *     within f.
 * @return {boolean} true if any element passes the test.
 * @template T,S
 */
goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
                  goog.array.ARRAY_PROTOTYPE_.some ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
          return true;
        }
      }
      return false;
    };


/**
 * Call f for each element of an array. If all calls return true, every()
 * returns true. If any call returns false, every() returns false and
 * does not continue to check the remaining elements.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {boolean} false if any element fails the test.
 * @template T,S
 */
goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
                   goog.array.ARRAY_PROTOTYPE_.every ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
          return false;
        }
      }
      return true;
    };


/**
 * Counts the array elements that fulfill the predicate, i.e. for which the
 * callback function returns true. Skips holes in the array.
 *
 * @param {!(Array.<T>|goog.array.ArrayLike)} arr Array or array like object
 *     over which to iterate.
 * @param {function(this: S, T, number, ?): boolean} f The function to call for
 *     every element. Takes 3 arguments (the element, the index and the array).
 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
 * @return {number} The number of the matching elements.
 * @template T,S
 */
goog.array.count = function(arr, f, opt_obj) {
  var count = 0;
  goog.array.forEach(arr, function(element, index, arr) {
    if (f.call(opt_obj, element, index, arr)) {
      ++count;
    }
  }, opt_obj);
  return count;
};


/**
 * Search an array for the first element that satisfies a given condition and
 * return that element.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {T} The first array element that passes the test, or null if no
 *     element is found.
 * @template T,S
 */
goog.array.find = function(arr, f, opt_obj) {
  var i = goog.array.findIndex(arr, f, opt_obj);
  return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
};


/**
 * Search an array for the first element that satisfies a given condition and
 * return its index.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {number} The index of the first array element that passes the test,
 *     or -1 if no element is found.
 * @template T,S
 */
goog.array.findIndex = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = 0; i < l; i++) {
    if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
      return i;
    }
  }
  return -1;
};


/**
 * Search an array (in reverse order) for the last element that satisfies a
 * given condition and return that element.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {T} The last array element that passes the test, or null if no
 *     element is found.
 * @template T,S
 */
goog.array.findRight = function(arr, f, opt_obj) {
  var i = goog.array.findIndexRight(arr, f, opt_obj);
  return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
};


/**
 * Search an array (in reverse order) for the last element that satisfies a
 * given condition and return its index.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {Object=} opt_obj An optional "this" context for the function.
 * @return {number} The index of the last array element that passes the test,
 *     or -1 if no element is found.
 * @template T,S
 */
goog.array.findIndexRight = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = l - 1; i >= 0; i--) {
    if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
      return i;
    }
  }
  return -1;
};


/**
 * Whether the array contains the given object.
 * @param {goog.array.ArrayLike} arr The array to test for the presence of the
 *     element.
 * @param {*} obj The object for which to test.
 * @return {boolean} true if obj is present.
 */
goog.array.contains = function(arr, obj) {
  return goog.array.indexOf(arr, obj) >= 0;
};


/**
 * Whether the array is empty.
 * @param {goog.array.ArrayLike} arr The array to test.
 * @return {boolean} true if empty.
 */
goog.array.isEmpty = function(arr) {
  return arr.length == 0;
};


/**
 * Clears the array.
 * @param {goog.array.ArrayLike} arr Array or array like object to clear.
 */
goog.array.clear = function(arr) {
  // For non real arrays we don't have the magic length so we delete the
  // indices.
  if (!goog.isArray(arr)) {
    for (var i = arr.length - 1; i >= 0; i--) {
      delete arr[i];
    }
  }
  arr.length = 0;
};


/**
 * Pushes an item into an array, if it's not already in the array.
 * @param {Array.<T>} arr Array into which to insert the item.
 * @param {T} obj Value to add.
 * @template T
 */
goog.array.insert = function(arr, obj) {
  if (!goog.array.contains(arr, obj)) {
    arr.push(obj);
  }
};


/**
 * Inserts an object at the given index of the array.
 * @param {goog.array.ArrayLike} arr The array to modify.
 * @param {*} obj The object to insert.
 * @param {number=} opt_i The index at which to insert the object. If omitted,
 *      treated as 0. A negative index is counted from the end of the array.
 */
goog.array.insertAt = function(arr, obj, opt_i) {
  goog.array.splice(arr, opt_i, 0, obj);
};


/**
 * Inserts at the given index of the array, all elements of another array.
 * @param {goog.array.ArrayLike} arr The array to modify.
 * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
 * @param {number=} opt_i The index at which to insert the object. If omitted,
 *      treated as 0. A negative index is counted from the end of the array.
 */
goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
  goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
};


/**
 * Inserts an object into an array before a specified object.
 * @param {Array.<T>} arr The array to modify.
 * @param {T} obj The object to insert.
 * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
 *     is omitted or not found, obj is inserted at the end of the array.
 * @template T
 */
goog.array.insertBefore = function(arr, obj, opt_obj2) {
  var i;
  if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
    arr.push(obj);
  } else {
    goog.array.insertAt(arr, obj, i);
  }
};


/**
 * Removes the first occurrence of a particular value from an array.
 * @param {goog.array.ArrayLike} arr Array from which to remove value.
 * @param {*} obj Object to remove.
 * @return {boolean} True if an element was removed.
 */
goog.array.remove = function(arr, obj) {
  var i = goog.array.indexOf(arr, obj);
  var rv;
  if ((rv = i >= 0)) {
    goog.array.removeAt(arr, i);
  }
  return rv;
};


/**
 * Removes from an array the element at index i
 * @param {goog.array.ArrayLike} arr Array or array like object from which to
 *     remove value.
 * @param {number} i The index to remove.
 * @return {boolean} True if an element was removed.
 */
goog.array.removeAt = function(arr, i) {
  goog.asserts.assert(arr.length != null);

  // use generic form of splice
  // splice returns the removed items and if successful the length of that
  // will be 1
  return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
};


/**
 * Removes the first value that satisfies the given condition.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {boolean} True if an element was removed.
 * @template T,S
 */
goog.array.removeIf = function(arr, f, opt_obj) {
  var i = goog.array.findIndex(arr, f, opt_obj);
  if (i >= 0) {
    goog.array.removeAt(arr, i);
    return true;
  }
  return false;
};


/**
 * Returns a new array that is the result of joining the arguments.  If arrays
 * are passed then their items are added, however, if non-arrays are passed they
 * will be added to the return array as is.
 *
 * Note that ArrayLike objects will be added as is, rather than having their
 * items added.
 *
 * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
 * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
 * goog.array.concat([1, 2], null) -> [1, 2, null]
 *
 * There is bug in all current versions of IE (6, 7 and 8) where arrays created
 * in an iframe become corrupted soon (not immediately) after the iframe is
 * destroyed. This is common if loading data via goog.net.IframeIo, for example.
 * This corruption only affects the concat method which will start throwing
 * Catastrophic Errors (#-2147418113).
 *
 * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
 *
 * Internally goog.array should use this, so that all methods will continue to
 * work on these broken array objects.
 *
 * @param {...*} var_args Items to concatenate.  Arrays will have each item
 *     added, while primitives and objects will be added as is.
 * @return {!Array} The new resultant array.
 */
goog.array.concat = function(var_args) {
  return goog.array.ARRAY_PROTOTYPE_.concat.apply(
      goog.array.ARRAY_PROTOTYPE_, arguments);
};


/**
 * Converts an object to an array.
 * @param {goog.array.ArrayLike} object  The object to convert to an array.
 * @return {!Array} The object converted into an array. If object has a
 *     length property, every property indexed with a non-negative number
 *     less than length will be included in the result. If object does not
 *     have a length property, an empty array will be returned.
 */
goog.array.toArray = function(object) {
  var length = object.length;

  // If length is not a number the following it false. This case is kept for
  // backwards compatibility since there are callers that pass objects that are
  // not array like.
  if (length > 0) {
    var rv = new Array(length);
    for (var i = 0; i < length; i++) {
      rv[i] = object[i];
    }
    return rv;
  }
  return [];
};


/**
 * Does a shallow copy of an array.
 * @param {goog.array.ArrayLike} arr  Array or array-like object to clone.
 * @return {!Array} Clone of the input array.
 */
goog.array.clone = goog.array.toArray;


/**
 * Extends an array with another array, element, or "array like" object.
 * This function operates 'in-place', it does not create a new Array.
 *
 * Example:
 * var a = [];
 * goog.array.extend(a, [0, 1]);
 * a; // [0, 1]
 * goog.array.extend(a, 2);
 * a; // [0, 1, 2]
 *
 * @param {Array} arr1  The array to modify.
 * @param {...*} var_args The elements or arrays of elements to add to arr1.
 */
goog.array.extend = function(arr1, var_args) {
  for (var i = 1; i < arguments.length; i++) {
    var arr2 = arguments[i];
    // If we have an Array or an Arguments object we can just call push
    // directly.
    var isArrayLike;
    if (goog.isArray(arr2) ||
        // Detect Arguments. ES5 says that the [[Class]] of an Arguments object
        // is "Arguments" but only V8 and JSC/Safari gets this right. We instead
        // detect Arguments by checking for array like and presence of "callee".
        (isArrayLike = goog.isArrayLike(arr2)) &&
            // The getter for callee throws an exception in strict mode
            // according to section 10.6 in ES5 so check for presence instead.
            Object.prototype.hasOwnProperty.call(arr2, 'callee')) {
      arr1.push.apply(arr1, arr2);
    } else if (isArrayLike) {
      // Otherwise loop over arr2 to prevent copying the object.
      var len1 = arr1.length;
      var len2 = arr2.length;
      for (var j = 0; j < len2; j++) {
        arr1[len1 + j] = arr2[j];
      }
    } else {
      arr1.push(arr2);
    }
  }
};


/**
 * Adds or removes elements from an array. This is a generic version of Array
 * splice. This means that it might work on other objects similar to arrays,
 * such as the arguments object.
 *
 * @param {goog.array.ArrayLike} arr The array to modify.
 * @param {number|undefined} index The index at which to start changing the
 *     array. If not defined, treated as 0.
 * @param {number} howMany How many elements to remove (0 means no removal. A
 *     value below 0 is treated as zero and so is any other non number. Numbers
 *     are floored).
 * @param {...*} var_args Optional, additional elements to insert into the
 *     array.
 * @return {!Array} the removed elements.
 */
goog.array.splice = function(arr, index, howMany, var_args) {
  goog.asserts.assert(arr.length != null);

  return goog.array.ARRAY_PROTOTYPE_.splice.apply(
      arr, goog.array.slice(arguments, 1));
};


/**
 * Returns a new array from a segment of an array. This is a generic version of
 * Array slice. This means that it might work on other objects similar to
 * arrays, such as the arguments object.
 *
 * @param {Array.<T>|goog.array.ArrayLike} arr The array from
 * which to copy a segment.
 * @param {number} start The index of the first element to copy.
 * @param {number=} opt_end The index after the last element to copy.
 * @return {!Array.<T>} A new array containing the specified segment of the
 *     original array.
 * @template T
 */
goog.array.slice = function(arr, start, opt_end) {
  goog.asserts.assert(arr.length != null);

  // passing 1 arg to slice is not the same as passing 2 where the second is
  // null or undefined (in that case the second argument is treated as 0).
  // we could use slice on the arguments object and then use apply instead of
  // testing the length
  if (arguments.length <= 2) {
    return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
  } else {
    return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
  }
};


/**
 * Removes all duplicates from an array (retaining only the first
 * occurrence of each array element).  This function modifies the
 * array in place and doesn't change the order of the non-duplicate items.
 *
 * For objects, duplicates are identified as having the same unique ID as
 * defined by {@link goog.getUid}.
 *
 * Runtime: N,
 * Worstcase space: 2N (no dupes)
 *
 * @param {goog.array.ArrayLike} arr The array from which to remove duplicates.
 * @param {Array=} opt_rv An optional array in which to return the results,
 *     instead of performing the removal inplace.  If specified, the original
 *     array will remain unchanged.
 */
goog.array.removeDuplicates = function(arr, opt_rv) {
  var returnArray = opt_rv || arr;

  var seen = {}, cursorInsert = 0, cursorRead = 0;
  while (cursorRead < arr.length) {
    var current = arr[cursorRead++];

    // Prefix each type with a single character representing the type to
    // prevent conflicting keys (e.g. true and 'true').
    var key = goog.isObject(current) ?
        'o' + goog.getUid(current) :
        (typeof current).charAt(0) + current;

    if (!Object.prototype.hasOwnProperty.call(seen, key)) {
      seen[key] = true;
      returnArray[cursorInsert++] = current;
    }
  }
  returnArray.length = cursorInsert;
};


/**
 * Searches the specified array for the specified target using the binary
 * search algorithm.  If no opt_compareFn is specified, elements are compared
 * using <code>goog.array.defaultCompare</code>, which compares the elements
 * using the built in < and > operators.  This will produce the expected
 * behavior for homogeneous arrays of String(s) and Number(s). The array
 * specified <b>must</b> be sorted in ascending order (as defined by the
 * comparison function).  If the array is not sorted, results are undefined.
 * If the array contains multiple instances of the specified target value, any
 * of these instances may be found.
 *
 * Runtime: O(log n)
 *
 * @param {goog.array.ArrayLike} arr The array to be searched.
 * @param {*} target The sought value.
 * @param {Function=} opt_compareFn Optional comparison function by which the
 *     array is ordered. Should take 2 arguments to compare, and return a
 *     negative number, zero, or a positive number depending on whether the
 *     first argument is less than, equal to, or greater than the second.
 * @return {number} Lowest index of the target value if found, otherwise
 *     (-(insertion point) - 1). The insertion point is where the value should
 *     be inserted into arr to preserve the sorted property.  Return value >= 0
 *     iff target is found.
 */
goog.array.binarySearch = function(arr, target, opt_compareFn) {
  return goog.array.binarySearch_(arr,
      opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
      target);
};


/**
 * Selects an index in the specified array using the binary search algorithm.
 * The evaluator receives an element and determines whether the desired index
 * is before, at, or after it.  The evaluator must be consistent (formally,
 * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
 * must be monotonically non-increasing).
 *
 * Runtime: O(log n)
 *
 * @param {goog.array.ArrayLike} arr The array to be searched.
 * @param {Function} evaluator Evaluator function that receives 3 arguments
 *     (the element, the index and the array). Should return a negative number,
 *     zero, or a positive number depending on whether the desired index is
 *     before, at, or after the element passed to it.
 * @param {Object=} opt_obj The object to be used as the value of 'this'
 *     within evaluator.
 * @return {number} Index of the leftmost element matched by the evaluator, if
 *     such exists; otherwise (-(insertion point) - 1). The insertion point is
 *     the index of the first element for which the evaluator returns negative,
 *     or arr.length if no such element exists. The return value is non-negative
 *     iff a match is found.
 */
goog.array.binarySelect = function(arr, evaluator, opt_obj) {
  return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
      undefined /* opt_target */, opt_obj);
};


/**
 * Implementation of a binary search algorithm which knows how to use both
 * comparison functions and evaluators. If an evaluator is provided, will call
 * the evaluator with the given optional data object, conforming to the
 * interface defined in binarySelect. Otherwise, if a comparison function is
 * provided, will call the comparison function against the given data object.
 *
 * This implementation purposefully does not use goog.bind or goog.partial for
 * performance reasons.
 *
 * Runtime: O(log n)
 *
 * @param {goog.array.ArrayLike} arr The array to be searched.
 * @param {Function} compareFn Either an evaluator or a comparison function,
 *     as defined by binarySearch and binarySelect above.
 * @param {boolean} isEvaluator Whether the function is an evaluator or a
 *     comparison function.
 * @param {*=} opt_target If the function is a comparison function, then this is
 *     the target to binary search for.
 * @param {Object=} opt_selfObj If the function is an evaluator, this is an
  *    optional this object for the evaluator.
 * @return {number} Lowest index of the target value if found, otherwise
 *     (-(insertion point) - 1). The insertion point is where the value should
 *     be inserted into arr to preserve the sorted property.  Return value >= 0
 *     iff target is found.
 * @private
 */
goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
    opt_selfObj) {
  var left = 0;  // inclusive
  var right = arr.length;  // exclusive
  var found;
  while (left < right) {
    var middle = (left + right) >> 1;
    var compareResult;
    if (isEvaluator) {
      compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
    } else {
      compareResult = compareFn(opt_target, arr[middle]);
    }
    if (compareResult > 0) {
      left = middle + 1;
    } else {
      right = middle;
      // We are looking for the lowest index so we can't return immediately.
      found = !compareResult;
    }
  }
  // left is the index if found, or the insertion point otherwise.
  // ~left is a shorthand for -left - 1.
  return found ? left : ~left;
};


/**
 * Sorts the specified array into ascending order.  If no opt_compareFn is
 * specified, elements are compared using
 * <code>goog.array.defaultCompare</code>, which compares the elements using
 * the built in < and > operators.  This will produce the expected behavior
 * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
 * but will give unpredictable results for heterogenous lists of strings and
 * numbers with different numbers of digits.
 *
 * This sort is not guaranteed to be stable.
 *
 * Runtime: Same as <code>Array.prototype.sort</code>
 *
 * @param {Array.<T>} arr The array to be sorted.
 * @param {?function(T,T):number=} opt_compareFn Optional comparison
 *     function by which the
 *     array is to be ordered. Should take 2 arguments to compare, and return a
 *     negative number, zero, or a positive number depending on whether the
 *     first argument is less than, equal to, or greater than the second.
 * @template T
 */
goog.array.sort = function(arr, opt_compareFn) {
  // TODO(arv): Update type annotation since null is not accepted.
  goog.asserts.assert(arr.length != null);

  goog.array.ARRAY_PROTOTYPE_.sort.call(
      arr, opt_compareFn || goog.array.defaultCompare);
};


/**
 * Sorts the specified array into ascending order in a stable way.  If no
 * opt_compareFn is specified, elements are compared using
 * <code>goog.array.defaultCompare</code>, which compares the elements using
 * the built in < and > operators.  This will produce the expected behavior
 * for homogeneous arrays of String(s) and Number(s).
 *
 * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
 * O(n) overhead of copying the array twice.
 *
 * @param {Array.<T>} arr The array to be sorted.
 * @param {?function(T, T): number=} opt_compareFn Optional comparison function
 *     by which the array is to be ordered. Should take 2 arguments to compare,
 *     and return a negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 * @template T
 */
goog.array.stableSort = function(arr, opt_compareFn) {
  for (var i = 0; i < arr.length; i++) {
    arr[i] = {index: i, value: arr[i]};
  }
  var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
  function stableCompareFn(obj1, obj2) {
    return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
  };
  goog.array.sort(arr, stableCompareFn);
  for (var i = 0; i < arr.length; i++) {
    arr[i] = arr[i].value;
  }
};


/**
 * Sorts an array of objects by the specified object key and compare
 * function. If no compare function is provided, the key values are
 * compared in ascending order using <code>goog.array.defaultCompare</code>.
 * This won't work for keys that get renamed by the compiler. So use
 * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
 * @param {Array.<Object>} arr An array of objects to sort.
 * @param {string} key The object key to sort by.
 * @param {Function=} opt_compareFn The function to use to compare key
 *     values.
 */
goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
  var compare = opt_compareFn || goog.array.defaultCompare;
  goog.array.sort(arr, function(a, b) {
    return compare(a[key], b[key]);
  });
};


/**
 * Tells if the array is sorted.
 * @param {!Array.<T>} arr The array.
 * @param {?function(T,T):number=} opt_compareFn Function to compare the
 *     array elements.
 *     Should take 2 arguments to compare, and return a negative number, zero,
 *     or a positive number depending on whether the first argument is less
 *     than, equal to, or greater than the second.
 * @param {boolean=} opt_strict If true no equal elements are allowed.
 * @return {boolean} Whether the array is sorted.
 * @template T
 */
goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
  var compare = opt_compareFn || goog.array.defaultCompare;
  for (var i = 1; i < arr.length; i++) {
    var compareResult = compare(arr[i - 1], arr[i]);
    if (compareResult > 0 || compareResult == 0 && opt_strict) {
      return false;
    }
  }
  return true;
};


/**
 * Compares two arrays for equality. Two arrays are considered equal if they
 * have the same length and their corresponding elements are equal according to
 * the comparison function.
 *
 * @param {goog.array.ArrayLike} arr1 The first array to compare.
 * @param {goog.array.ArrayLike} arr2 The second array to compare.
 * @param {Function=} opt_equalsFn Optional comparison function.
 *     Should take 2 arguments to compare, and return true if the arguments
 *     are equal. Defaults to {@link goog.array.defaultCompareEquality} which
 *     compares the elements using the built-in '===' operator.
 * @return {boolean} Whether the two arrays are equal.
 */
goog.array.equals = function(arr1, arr2, opt_equalsFn) {
  if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
      arr1.length != arr2.length) {
    return false;
  }
  var l = arr1.length;
  var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
  for (var i = 0; i < l; i++) {
    if (!equalsFn(arr1[i], arr2[i])) {
      return false;
    }
  }
  return true;
};


/**
 * @deprecated Use {@link goog.array.equals}.
 * @param {goog.array.ArrayLike} arr1 See {@link goog.array.equals}.
 * @param {goog.array.ArrayLike} arr2 See {@link goog.array.equals}.
 * @param {Function=} opt_equalsFn See {@link goog.array.equals}.
 * @return {boolean} See {@link goog.array.equals}.
 */
goog.array.compare = function(arr1, arr2, opt_equalsFn) {
  return goog.array.equals(arr1, arr2, opt_equalsFn);
};


/**
 * 3-way array compare function.
 * @param {!goog.array.ArrayLike} arr1 The first array to compare.
 * @param {!goog.array.ArrayLike} arr2 The second array to compare.
 * @param {?function(?, ?): number=} opt_compareFn Optional comparison function
 *     by which the array is to be ordered. Should take 2 arguments to compare,
 *     and return a negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 * @return {number} Negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 */
goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
  var compare = opt_compareFn || goog.array.defaultCompare;
  var l = Math.min(arr1.length, arr2.length);
  for (var i = 0; i < l; i++) {
    var result = compare(arr1[i], arr2[i]);
    if (result != 0) {
      return result;
    }
  }
  return goog.array.defaultCompare(arr1.length, arr2.length);
};


/**
 * Compares its two arguments for order, using the built in < and >
 * operators.
 * @param {*} a The first object to be compared.
 * @param {*} b The second object to be compared.
 * @return {number} A negative number, zero, or a positive number as the first
 *     argument is less than, equal to, or greater than the second.
 */
goog.array.defaultCompare = function(a, b) {
  return a > b ? 1 : a < b ? -1 : 0;
};


/**
 * Compares its two arguments for equality, using the built in === operator.
 * @param {*} a The first object to compare.
 * @param {*} b The second object to compare.
 * @return {boolean} True if the two arguments are equal, false otherwise.
 */
goog.array.defaultCompareEquality = function(a, b) {
  return a === b;
};


/**
 * Inserts a value into a sorted array. The array is not modified if the
 * value is already present.
 * @param {Array.<T>} array The array to modify.
 * @param {T} value The object to insert.
 * @param {?function(T,T):number=} opt_compareFn Optional comparison function by
 *     which the
 *     array is ordered. Should take 2 arguments to compare, and return a
 *     negative number, zero, or a positive number depending on whether the
 *     first argument is less than, equal to, or greater than the second.
 * @return {boolean} True if an element was inserted.
 * @template T
 */
goog.array.binaryInsert = function(array, value, opt_compareFn) {
  var index = goog.array.binarySearch(array, value, opt_compareFn);
  if (index < 0) {
    goog.array.insertAt(array, value, -(index + 1));
    return true;
  }
  return false;
};


/**
 * Removes a value from a sorted array.
 * @param {Array} array The array to modify.
 * @param {*} value The object to remove.
 * @param {Function=} opt_compareFn Optional comparison function by which the
 *     array is ordered. Should take 2 arguments to compare, and return a
 *     negative number, zero, or a positive number depending on whether the
 *     first argument is less than, equal to, or greater than the second.
 * @return {boolean} True if an element was removed.
 */
goog.array.binaryRemove = function(array, value, opt_compareFn) {
  var index = goog.array.binarySearch(array, value, opt_compareFn);
  return (index >= 0) ? goog.array.removeAt(array, index) : false;
};


/**
 * Splits an array into disjoint buckets according to a splitting function.
 * @param {Array.<T>} array The array.
 * @param {function(this:S, T,number,Array.<T>):?} sorter Function to call for
 *     every element.  This takes 3 arguments (the element, the index and the
 *     array) and must return a valid object key (a string, number, etc), or
 *     undefined, if that object should not be placed in a bucket.
 * @param {S=} opt_obj The object to be used as the value of 'this' within
 *     sorter.
 * @return {!Object} An object, with keys being all of the unique return values
 *     of sorter, and values being arrays containing the items for
 *     which the splitter returned that key.
 * @template T,S
 */
goog.array.bucket = function(array, sorter, opt_obj) {
  var buckets = {};

  for (var i = 0; i < array.length; i++) {
    var value = array[i];
    var key = sorter.call(opt_obj, value, i, array);
    if (goog.isDef(key)) {
      // Push the value to the right bucket, creating it if necessary.
      var bucket = buckets[key] || (buckets[key] = []);
      bucket.push(value);
    }
  }

  return buckets;
};


/**
 * Creates a new object built from the provided array and the key-generation
 * function.
 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
 *     which to iterate whose elements will be the values in the new object.
 * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
 *     call for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a string that will be used as the
 *     key for the element in the new object. If the function returns the same
 *     key for more than one element, the value for that key is
 *     implementation-defined.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within keyFunc.
 * @return {!Object.<T>} The new object.
 * @template T,S
 */
goog.array.toObject = function(arr, keyFunc, opt_obj) {
  var ret = {};
  goog.array.forEach(arr, function(element, index) {
    ret[keyFunc.call(opt_obj, element, index, arr)] = element;
  });
  return ret;
};


/**
 * Creates a range of numbers in an arithmetic progression.
 *
 * Range takes 1, 2, or 3 arguments:
 * <pre>
 * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
 * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
 * range(-2, -5, -1) produces [-2, -3, -4]
 * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
 * </pre>
 *
 * @param {number} startOrEnd The starting value of the range if an end argument
 *     is provided. Otherwise, the start value is 0, and this is the end value.
 * @param {number=} opt_end The optional end value of the range.
 * @param {number=} opt_step The step size between range values. Defaults to 1
 *     if opt_step is undefined or 0.
 * @return {!Array.<number>} An array of numbers for the requested range. May be
 *     an empty array if adding the step would not converge toward the end
 *     value.
 */
goog.array.range = function(startOrEnd, opt_end, opt_step) {
  var array = [];
  var start = 0;
  var end = startOrEnd;
  var step = opt_step || 1;
  if (opt_end !== undefined) {
    start = startOrEnd;
    end = opt_end;
  }

  if (step * (end - start) < 0) {
    // Sign mismatch: start + step will never reach the end value.
    return [];
  }

  if (step > 0) {
    for (var i = start; i < end; i += step) {
      array.push(i);
    }
  } else {
    for (var i = start; i > end; i += step) {
      array.push(i);
    }
  }
  return array;
};


/**
 * Returns an array consisting of the given value repeated N times.
 *
 * @param {*} value The value to repeat.
 * @param {number} n The repeat count.
 * @return {!Array} An array with the repeated value.
 */
goog.array.repeat = function(value, n) {
  var array = [];
  for (var i = 0; i < n; i++) {
    array[i] = value;
  }
  return array;
};


/**
 * Returns an array consisting of every argument with all arrays
 * expanded in-place recursively.
 *
 * @param {...*} var_args The values to flatten.
 * @return {!Array} An array containing the flattened values.
 */
goog.array.flatten = function(var_args) {
  var result = [];
  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (goog.isArray(element)) {
      result.push.apply(result, goog.array.flatten.apply(null, element));
    } else {
      result.push(element);
    }
  }
  return result;
};


/**
 * Rotates an array in-place. After calling this method, the element at
 * index i will be the element previously at index (i - n) %
 * array.length, for all values of i between 0 and array.length - 1,
 * inclusive.
 *
 * For example, suppose list comprises [t, a, n, k, s]. After invoking
 * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
 *
 * @param {!Array.<T>} array The array to rotate.
 * @param {number} n The amount to rotate.
 * @return {!Array.<T>} The array.
 * @template T
 */
goog.array.rotate = function(array, n) {
  goog.asserts.assert(array.length != null);

  if (array.length) {
    n %= array.length;
    if (n > 0) {
      goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
    } else if (n < 0) {
      goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
    }
  }
  return array;
};


/**
 * Creates a new array for which the element at position i is an array of the
 * ith element of the provided arrays.  The returned array will only be as long
 * as the shortest array provided; additional values are ignored.  For example,
 * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
 *
 * This is similar to the zip() function in Python.  See {@link
 * http://docs.python.org/library/functions.html#zip}
 *
 * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
 * @return {!Array.<!Array>} A new array of arrays created from provided arrays.
 */
goog.array.zip = function(var_args) {
  if (!arguments.length) {
    return [];
  }
  var result = [];
  for (var i = 0; true; i++) {
    var value = [];
    for (var j = 0; j < arguments.length; j++) {
      var arr = arguments[j];
      // If i is larger than the array length, this is the shortest array.
      if (i >= arr.length) {
        return result;
      }
      value.push(arr[i]);
    }
    result.push(value);
  }
};


/**
 * Shuffles the values in the specified array using the Fisher-Yates in-place
 * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
 * and so resets the state of that random number generator. Similarly, may reset
 * the state of the any other specified random number generator.
 *
 * Runtime: O(n)
 *
 * @param {!Array} arr The array to be shuffled.
 * @param {function():number=} opt_randFn Optional random function to use for
 *     shuffling.
 *     Takes no arguments, and returns a random number on the interval [0, 1).
 *     Defaults to Math.random() using JavaScript's built-in Math library.
 */
goog.array.shuffle = function(arr, opt_randFn) {
  var randFn = opt_randFn || Math.random;

  for (var i = arr.length - 1; i > 0; i--) {
    // Choose a random array index in [0, i] (inclusive with i).
    var j = Math.floor(randFn() * (i + 1));

    var tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }
};

// Input 5
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for creating functions. Loosely inspired by the
 * java classes: http://go/functions.java and http://go/predicate.java.
 *
 * @author nicksantos@google.com (Nick Santos)
 */


goog.provide('goog.functions');


/**
 * Creates a function that always returns the same value.
 * @param {T} retValue The value to return.
 * @return {function():T} The new function.
 * @template T
 */
goog.functions.constant = function(retValue) {
  return function() {
    return retValue;
  };
};


/**
 * Always returns false.
 * @type {function(...): boolean}
 */
goog.functions.FALSE = goog.functions.constant(false);


/**
 * Always returns true.
 * @type {function(...): boolean}
 */
goog.functions.TRUE = goog.functions.constant(true);


/**
 * Always returns NULL.
 * @type {function(...): null}
 */
goog.functions.NULL = goog.functions.constant(null);


/**
 * A simple function that returns the first argument of whatever is passed
 * into it.
 * @param {T=} opt_returnValue The single value that will be returned.
 * @param {...*} var_args Optional trailing arguments. These are ignored.
 * @return {T} The first argument passed in, or undefined if nothing was passed.
 * @template T
 */
goog.functions.identity = function(opt_returnValue, var_args) {
  return opt_returnValue;
};


/**
 * Creates a function that always throws an error with the given message.
 * @param {string} message The error message.
 * @return {!Function} The error-throwing function.
 */
goog.functions.error = function(message) {
  return function() {
    throw Error(message);
  };
};


/**
 * Creates a function that throws the given object.
 * @param {*} err An object to be thrown.
 * @return {!Function} The error-throwing function.
 */
goog.functions.fail = function(err) {
  return function() {
    throw err;
  }
};


/**
 * Given a function, create a function that keeps opt_numArgs arguments and
 * silently discards all additional arguments.
 * @param {Function} f The original function.
 * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
 * @return {!Function} A version of f that only keeps the first opt_numArgs
 *     arguments.
 */
goog.functions.lock = function(f, opt_numArgs) {
  opt_numArgs = opt_numArgs || 0;
  return function() {
    return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
  };
};


/**
 * Given a function, create a new function that swallows its return value
 * and replaces it with a new one.
 * @param {Function} f A function.
 * @param {T} retValue A new return value.
 * @return {function(...[?]):T} A new function.
 * @template T
 */
goog.functions.withReturnValue = function(f, retValue) {
  return goog.functions.sequence(f, goog.functions.constant(retValue));
};


/**
 * Creates the composition of the functions passed in.
 * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
 * @param {function(...[?]):T} fn The final function.
 * @param {...Function} var_args A list of functions.
 * @return {function(...[?]):T} The composition of all inputs.
 * @template T
 */
goog.functions.compose = function(fn, var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    var result;
    if (length) {
      result = functions[length - 1].apply(this, arguments);
    }

    for (var i = length - 2; i >= 0; i--) {
      result = functions[i].call(this, result);
    }
    return result;
  };
};


/**
 * Creates a function that calls the functions passed in in sequence, and
 * returns the value of the last function. For example,
 * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
 * @param {...Function} var_args A list of functions.
 * @return {!Function} A function that calls all inputs in sequence.
 */
goog.functions.sequence = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    var result;
    for (var i = 0; i < length; i++) {
      result = functions[i].apply(this, arguments);
    }
    return result;
  };
};


/**
 * Creates a function that returns true if each of its components evaluates
 * to true. The components are evaluated in order, and the evaluation will be
 * short-circuited as soon as a function returns false.
 * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
 * @param {...Function} var_args A list of functions.
 * @return {function(...[?]):boolean} A function that ANDs its component
 *      functions.
 */
goog.functions.and = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    for (var i = 0; i < length; i++) {
      if (!functions[i].apply(this, arguments)) {
        return false;
      }
    }
    return true;
  };
};


/**
 * Creates a function that returns true if any of its components evaluates
 * to true. The components are evaluated in order, and the evaluation will be
 * short-circuited as soon as a function returns true.
 * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
 * @param {...Function} var_args A list of functions.
 * @return {function(...[?]):boolean} A function that ORs its component
 *    functions.
 */
goog.functions.or = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    for (var i = 0; i < length; i++) {
      if (functions[i].apply(this, arguments)) {
        return true;
      }
    }
    return false;
  };
};


/**
 * Creates a function that returns the Boolean opposite of a provided function.
 * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
 * @param {!Function} f The original function.
 * @return {function(...[?]):boolean} A function that delegates to f and returns
 * opposite.
 */
goog.functions.not = function(f) {
  return function() {
    return !f.apply(this, arguments);
  };
};


/**
 * Generic factory function to construct an object given the constructor
 * and the arguments. Intended to be bound to create object factories.
 *
 * Callers should cast the result to the appropriate type for proper type
 * checking by the compiler.
 * @param {!Function} constructor The constructor for the Object.
 * @param {...*} var_args The arguments to be passed to the constructor.
 * @return {!Object} A new instance of the class given in {@code constructor}.
 */
goog.functions.create = function(constructor, var_args) {
  /** @constructor */
  var temp = function() {};
  temp.prototype = constructor.prototype;

  // obj will have constructor's prototype in its chain and
  // 'obj instanceof constructor' will be true.
  var obj = new temp();

  // obj is initialized by constructor.
  // arguments is only array-like so lacks shift(), but can be used with
  // the Array prototype function.
  constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
  return obj;
};

// Input 6
// Copyright 2007 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors. All Rights
// Reserved.

/**
 * @license Portions of this code are from MochiKit, received by
 * The Closure Authors under the MIT license. All other code is Copyright
 * 2005-2009 The Closure Authors. All Rights Reserved.
 */

/**
 * @fileoverview Classes for tracking asynchronous operations and handling the
 * results. The Deferred object here is patterned after the Deferred object in
 * the Twisted python networking framework.
 *
 * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
 *
 * Based on the Dojo code which in turn is based on the MochiKit code.
 *
 */

goog.provide('goog.async.Deferred');
goog.provide('goog.async.Deferred.AlreadyCalledError');
goog.provide('goog.async.Deferred.CanceledError');
goog.provide('goog.async.Deferred.CancelledError');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');
goog.require('goog.functions');



/**
 * A Deferred represents the result of an asynchronous operation. A Deferred
 * instance has no result when it is created, and is "fired" (given an initial
 * result) by calling {@code callback} or {@code errback}.
 *
 * Once fired, the result is passed through a sequence of callback functions
 * registered with {@code addCallback} or {@code addErrback}. The functions may
 * mutate the result before it is passed to the next function in the sequence.
 *
 * Callbacks and errbacks may be added at any time, including after the Deferred
 * has been "fired". If there are no pending actions in the execution sequence
 * of a fired Deferred, any new callback functions will be called with the last
 * computed result. Adding a callback function is the only way to access the
 * result of the Deferred.
 *
 * If a Deferred operation is canceled, an optional user-provided cancellation
 * function is invoked which may perform any special cleanup, followed by firing
 * the Deferred's errback sequence with a {@code CanceledError}. If the
 * Deferred has already fired, cancellation is ignored.
 *
 * @param {Function=} opt_onCancelFunction A function that will be called if the
 *     Deferred is canceled. If provided, this function runs before the
 *     Deferred is fired with a {@code CanceledError}.
 * @param {Object=} opt_defaultScope The default object context to call
 *     callbacks and errbacks in.
 * @constructor
 */
goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
  /**
   * Entries in the sequence are arrays containing a callback, an errback, and
   * an optional scope. The callback or errback in an entry may be null.
   * @type {!Array.<!Array>}
   * @private
   */
  this.sequence_ = [];

  /**
   * Optional function that will be called if the Deferred is canceled.
   * @type {Function|undefined}
   * @private
   */
  this.onCancelFunction_ = opt_onCancelFunction;

  /**
   * The default scope to execute callbacks and errbacks in.
   * @type {Object}
   * @private
   */
  this.defaultScope_ = opt_defaultScope || null;
};


/**
 * Whether the Deferred has been fired.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.fired_ = false;


/**
 * Whether the last result in the execution sequence was an error.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.hadError_ = false;


/**
 * The current Deferred result, updated as callbacks and errbacks are executed.
 * @type {*}
 * @private
 */
goog.async.Deferred.prototype.result_;


/**
 * Whether the Deferred is blocked waiting on another Deferred to fire. If a
 * callback or errback returns a Deferred as a result, the execution sequence is
 * blocked until that Deferred result becomes available.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.blocked_ = false;


/**
 * Whether this Deferred is blocking execution of another Deferred. If this
 * instance was returned as a result in another Deferred's execution sequence,
 * that other Deferred becomes blocked until this instance's execution sequence
 * completes. No additional callbacks may be added to a Deferred once it
 * is blocking another instance.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.blocking_ = false;


/**
 * Whether the Deferred has been canceled without having a custom cancel
 * function.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.silentlyCanceled_ = false;


/**
 * If an error is thrown during Deferred execution with no errback to catch it,
 * the error is rethrown after a timeout. Reporting the error after a timeout
 * allows execution to continue in the calling context.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.unhandledExceptionTimeoutId_;


/**
 * If this Deferred was created by branch(), this will be the "parent" Deferred.
 * @type {goog.async.Deferred}
 * @private
 */
goog.async.Deferred.prototype.parent_;


/**
 * The number of Deferred objects that have been branched off this one. This
 * will be decremented whenever a branch is fired or canceled.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.branches_ = 0;


/**
 * @define {boolean} Whether unhandled errors should always get rethrown to the
 * global scope. Defaults to the value of goog.DEBUG.
 */
goog.async.Deferred.STRICT_ERRORS = false;


/**
 * Cancels a Deferred that has not yet been fired, or is blocked on another
 * deferred operation. If this Deferred is waiting for a blocking Deferred to
 * fire, the blocking Deferred will also be canceled.
 *
 * If this Deferred was created by calling branch() on a parent Deferred with
 * opt_propagateCancel set to true, the parent may also be canceled. If
 * opt_deepCancel is set, cancel() will be called on the parent (as well as any
 * other ancestors if the parent is also a branch). If one or more branches were
 * created with opt_propagateCancel set to true, the parent will be canceled if
 * cancel() is called on all of those branches.
 *
 * @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
 *     if cancel() hasn't been called on some of the parent's branches. Has no
 *     effect on a branch without opt_propagateCancel set to true.
 */
goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
  if (!this.hasFired()) {
    if (this.parent_) {
      // Get rid of the parent reference before potentially running the parent's
      // canceler function to ensure that this cancellation isn't
      // double-counted.
      var parent = this.parent_;
      delete this.parent_;
      if (opt_deepCancel) {
        parent.cancel(opt_deepCancel);
      } else {
        parent.branchCancel_();
      }
    }

    if (this.onCancelFunction_) {
      // Call in user-specified scope.
      this.onCancelFunction_.call(this.defaultScope_, this);
    } else {
      this.silentlyCanceled_ = true;
    }
    if (!this.hasFired()) {
      this.errback(new goog.async.Deferred.CanceledError(this));
    }
  } else if (this.result_ instanceof goog.async.Deferred) {
    this.result_.cancel();
  }
};


/**
 * Handle a single branch being canceled. Once all branches are canceled, this
 * Deferred will be canceled as well.
 *
 * @private
 */
goog.async.Deferred.prototype.branchCancel_ = function() {
  this.branches_--;
  if (this.branches_ <= 0) {
    this.cancel();
  }
};


/**
 * Called after a blocking Deferred fires. Unblocks this Deferred and resumes
 * its execution sequence.
 *
 * @param {boolean} isSuccess Whether the result is a success or an error.
 * @param {*} res The result of the blocking Deferred.
 * @private
 */
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
  this.blocked_ = false;
  this.updateResult_(isSuccess, res);
};


/**
 * Updates the current result based on the success or failure of the last action
 * in the execution sequence.
 *
 * @param {boolean} isSuccess Whether the new result is a success or an error.
 * @param {*} res The result.
 * @private
 */
goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
  this.fired_ = true;
  this.result_ = res;
  this.hadError_ = !isSuccess;
  this.fire_();
};


/**
 * Verifies that the Deferred has not yet been fired.
 *
 * @private
 * @throws {Error} If this has already been fired.
 */
goog.async.Deferred.prototype.check_ = function() {
  if (this.hasFired()) {
    if (!this.silentlyCanceled_) {
      throw new goog.async.Deferred.AlreadyCalledError(this);
    }
    this.silentlyCanceled_ = false;
  }
};


/**
 * Fire the execution sequence for this Deferred by passing the starting result
 * to the first registered callback.
 * @param {*=} opt_result The starting result.
 */
goog.async.Deferred.prototype.callback = function(opt_result) {
  this.check_();
  this.assertNotDeferred_(opt_result);
  this.updateResult_(true /* isSuccess */, opt_result);
};


/**
 * Fire the execution sequence for this Deferred by passing the starting error
 * result to the first registered errback.
 * @param {*=} opt_result The starting error.
 */
goog.async.Deferred.prototype.errback = function(opt_result) {
  this.check_();
  this.assertNotDeferred_(opt_result);
  this.updateResult_(false /* isSuccess */, opt_result);
};


/**
 * Asserts that an object is not a Deferred.
 * @param {*} obj The object to test.
 * @throws {Error} Throws an exception if the object is a Deferred.
 * @private
 */
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
  goog.asserts.assert(
      !(obj instanceof goog.async.Deferred),
      'An execution sequence may not be initiated with a blocking Deferred.');
};


/**
 * Register a callback function to be called with a successful result. If no
 * value is returned by the callback function, the result value is unchanged. If
 * a new value is returned, it becomes the Deferred result and will be passed to
 * the next callback in the execution sequence.
 *
 * If the function throws an error, the error becomes the new result and will be
 * passed to the next errback in the execution chain.
 *
 * If the function returns a Deferred, the execution sequence will be blocked
 * until that Deferred fires. Its result will be passed to the next callback (or
 * errback if it is an error result) in this Deferred's execution sequence.
 *
 * @param {!function(this:T,?):?} cb The function to be called with a successful
 *     result.
 * @param {T=} opt_scope An optional scope to call the callback in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
  return this.addCallbacks(cb, null, opt_scope);
};


/**
 * Register a callback function to be called with an error result. If no value
 * is returned by the function, the error result is unchanged. If a new error
 * value is returned or thrown, that error becomes the Deferred result and will
 * be passed to the next errback in the execution sequence.
 *
 * If the errback function handles the error by returning a non-error value,
 * that result will be passed to the next normal callback in the sequence.
 *
 * If the function returns a Deferred, the execution sequence will be blocked
 * until that Deferred fires. Its result will be passed to the next callback (or
 * errback if it is an error result) in this Deferred's execution sequence.
 *
 * @param {!function(this:T,?):?} eb The function to be called on an
 *     unsuccessful result.
 * @param {T=} opt_scope An optional scope to call the errback in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
  return this.addCallbacks(null, eb, opt_scope);
};


/**
 * Registers one function as both a callback and errback.
 *
 * @param {!function(this:T,?):?} f The function to be called on any result.
 * @param {T=} opt_scope An optional scope to call the function in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
  return this.addCallbacks(f, f, opt_scope);
};


/**
 * Registers a callback function and an errback function at the same position
 * in the execution sequence. Only one of these functions will execute,
 * depending on the error state during the execution sequence.
 *
 * NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
 * the callback is invoked, the errback will be skipped, and vice versa.
 *
 * @param {(function(this:T,?):?)|null} cb The function to be called on a
 *     successful result.
 * @param {(function(this:T,?):?)|null} eb The function to be called on an
 *     unsuccessful result.
 * @param {T=} opt_scope An optional scope to call the functions in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
  goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
  this.sequence_.push([cb, eb, opt_scope]);
  if (this.hasFired()) {
    this.fire_();
  }
  return this;
};


/**
 * Links another Deferred to the end of this Deferred's execution sequence. The
 * result of this execution sequence will be passed as the starting result for
 * the chained Deferred, invoking either its first callback or errback.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
 * @return {!goog.async.Deferred} This Deferred.
 */
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
  this.addCallbacks(
      otherDeferred.callback, otherDeferred.errback, otherDeferred);
  return this;
};


/**
 * Makes this Deferred wait for another Deferred's execution sequence to
 * complete before continuing.
 *
 * This is equivalent to adding a callback that returns {@code otherDeferred},
 * but doesn't prevent additional callbacks from being added to
 * {@code otherDeferred}.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to wait for.
 * @return {!goog.async.Deferred} This Deferred.
 */
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
  return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};


/**
 * Creates a branch off this Deferred's execution sequence, and returns it as a
 * new Deferred. The branched Deferred's starting result will be shared with the
 * parent at the point of the branch, even if further callbacks are added to the
 * parent.
 *
 * All branches at the same stage in the execution sequence will receive the
 * same starting value.
 *
 * @param {boolean=} opt_propagateCancel If cancel() is called on every child
 *     branch created with opt_propagateCancel, the parent will be canceled as
 *     well.
 * @return {!goog.async.Deferred} A Deferred that will be started with the
 *     computed result from this stage in the execution sequence.
 */
goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
  var d = new goog.async.Deferred();
  this.chainDeferred(d);
  if (opt_propagateCancel) {
    d.parent_ = this;
    this.branches_++;
  }
  return d;
};


/**
 * @return {boolean} Whether the execution sequence has been started on this
 *     Deferred by invoking {@code callback} or {@code errback}.
 */
goog.async.Deferred.prototype.hasFired = function() {
  return this.fired_;
};


/**
 * @param {*} res The latest result in the execution sequence.
 * @return {boolean} Whether the current result is an error that should cause
 *     the next errback to fire. May be overridden by subclasses to handle
 *     special error types.
 * @protected
 */
goog.async.Deferred.prototype.isError = function(res) {
  return res instanceof Error;
};


/**
 * @return {boolean} Whether an errback exists in the remaining sequence.
 * @private
 */
goog.async.Deferred.prototype.hasErrback_ = function() {
  return goog.array.some(this.sequence_, function(sequenceRow) {
    // The errback is the second element in the array.
    return goog.isFunction(sequenceRow[1]);
  });
};


/**
 * Exhausts the execution sequence while a result is available. The result may
 * be modified by callbacks or errbacks, and execution will block if the
 * returned result is an incomplete Deferred.
 *
 * @private
 */
goog.async.Deferred.prototype.fire_ = function() {
  if (this.unhandledExceptionTimeoutId_ && this.hasFired() &&
      this.hasErrback_()) {
    // It is possible to add errbacks after the Deferred has fired. If a new
    // errback is added immediately after the Deferred encountered an unhandled
    // error, but before that error is rethrown, cancel the rethrow.
    goog.global.clearTimeout(this.unhandledExceptionTimeoutId_);
    delete this.unhandledExceptionTimeoutId_;
  }

  if (this.parent_) {
    this.parent_.branches_--;
    delete this.parent_;
  }

  var res = this.result_;
  var unhandledException = false;
  var isNewlyBlocked = false;

  while (this.sequence_.length && !this.blocked_) {
    var sequenceEntry = this.sequence_.shift();

    var callback = sequenceEntry[0];
    var errback = sequenceEntry[1];
    var scope = sequenceEntry[2];

    var f = this.hadError_ ? errback : callback;
    if (f) {
      /** @preserveTry */
      try {
        var ret = f.call(scope || this.defaultScope_, res);

        // If no result, then use previous result.
        if (goog.isDef(ret)) {
          // Bubble up the error as long as the return value hasn't changed.
          this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
          this.result_ = res = ret;
        }

        if (res instanceof goog.async.Deferred) {
          isNewlyBlocked = true;
          this.blocked_ = true;
        }

      } catch (ex) {
        res = ex;
        this.hadError_ = true;

        if (!this.hasErrback_()) {
          // If an error is thrown with no additional errbacks in the queue,
          // prepare to rethrow the error.
          unhandledException = true;
        }
      }
    }
  }

  this.result_ = res;

  if (isNewlyBlocked) {
    res.addCallbacks(
        goog.bind(this.continue_, this, true /* isSuccess */),
        goog.bind(this.continue_, this, false /* isSuccess */));
    res.blocking_ = true;
  } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
      !(res instanceof goog.async.Deferred.CanceledError)) {
    this.hadError_ = true;
    unhandledException = true;
  }

  if (unhandledException) {
    // Rethrow the unhandled error after a timeout. Execution will continue, but
    // the error will be seen by global handlers and the user. The throw will
    // be canceled if another errback is appended before the timeout executes.
    // The error's original stack trace is preserved where available.
    this.unhandledExceptionTimeoutId_ = goog.global.setTimeout(
        goog.functions.fail(res), 0);
  }
};


/**
 * Creates a Deferred that has an initial result.
 *
 * @param {*=} opt_result The result.
 * @return {!goog.async.Deferred} The new Deferred.
 */
goog.async.Deferred.succeed = function(opt_result) {
  var d = new goog.async.Deferred();
  d.callback(opt_result);
  return d;
};


/**
 * Creates a Deferred that has an initial error result.
 *
 * @param {*} res The error result.
 * @return {!goog.async.Deferred} The new Deferred.
 */
goog.async.Deferred.fail = function(res) {
  var d = new goog.async.Deferred();
  d.errback(res);
  return d;
};


/**
 * Creates a Deferred that has already been canceled.
 *
 * @return {!goog.async.Deferred} The new Deferred.
 */
goog.async.Deferred.canceled = function() {
  var d = new goog.async.Deferred();
  d.cancel();
  return d;
};


/**
 * Creates a Deferred that has already been canceled. Aliases the preferred
 * spelling {@see goog.async.Deferred.canceled}.
 * @deprecated Renamed to goog.async.Deferred.canceled. Alias to be removed
 *     after 2013-04-03.
 */
goog.async.Deferred.cancelled = goog.async.Deferred.canceled;


/**
 * Normalizes values that may or may not be Deferreds.
 *
 * If the input value is a Deferred, the Deferred is branched (so the original
 * execution sequence is not modified) and the input callback added to the new
 * branch. The branch is returned to the caller.
 *
 * If the input value is not a Deferred, the callback will be executed
 * immediately and an already firing Deferred will be returned to the caller.
 *
 * In the following (contrived) example, if <code>isImmediate</code> is true
 * then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
 *
 * <pre>
 * var value;
 * if (isImmediate) {
 *   value = 3;
 * } else {
 *   value = new goog.async.Deferred();
 *   setTimeout(function() { value.callback(6); }, 2000);
 * }
 *
 * var d = goog.async.Deferred.when(value, alert);
 * </pre>
 *
 * @param {*} value Deferred or normal value to pass to the callback.
 * @param {!function(this:T, ?):?} callback The callback to execute.
 * @param {T=} opt_scope An optional scope to call the callback in.
 * @return {!goog.async.Deferred} A new Deferred that will call the input
 *     callback with the input value.
 * @template T
 */
goog.async.Deferred.when = function(value, callback, opt_scope) {
  if (value instanceof goog.async.Deferred) {
    return value.branch(true).addCallback(callback, opt_scope);
  } else {
    return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
  }
};


/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
goog.async.Error = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, goog.async.Error);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
};
goog.inherits(goog.async.Error, Error);



/**
 * An error sub class that is used when a Deferred has already been called.
 * @param {!goog.async.Deferred} deferred The Deferred.
 *
 * @constructor
 * @extends {goog.async.Error}
 */
goog.async.Deferred.AlreadyCalledError = function(deferred) {
  goog.async.Error.call(this);

  /**
   * The Deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.async.Error);


/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.message =
    'Deferred has already fired';


/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';



/**
 * An error sub class that is used when a Deferred is canceled.
 *
 * @param {!goog.async.Deferred} deferred The Deferred object.
 * @constructor
 * @extends {goog.async.Error}
 */
goog.async.Deferred.CanceledError = function(deferred) {
  goog.async.Error.call(this);

  /**
   * The Deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.CanceledError, goog.async.Error);


/** @override */
goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';


/** @override */
goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';


/**
 * An error sub class that is used when a Deferred is canceled. Aliases the
 * preferred spelling {@see goog.async.Deferred.CanceledError}.
 * @constructor
 * @extends {goog.debug.Error}
 * @deprecated Renamed to goog.async.Deferred.CanceledError. Alias to be removed
 *     after 2013-04-03.
 */
goog.async.Deferred.CancelledError = goog.async.Deferred.CanceledError;

// Input 7
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Utilities function for debug.
 *
 * NOTE: these code are stripped using compiler prefix feature.
 * See more detail in tools/strip_debug.txt file.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.debug.error.ArgumentException');
goog.provide('ydn.debug.error.TypeError');
goog.provide('ydn.debug.error.InternalError');
goog.provide('ydn.debug.error.InvalidOperationException');
goog.provide('ydn.debug.error.NotImplementedException');
goog.provide('ydn.debug.error.NotSupportedException');

goog.require('goog.debug.Error');



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {goog.debug.Error}
 */
ydn.debug.error.ArgumentException = function(opt_msg) {
  goog.base(this, opt_msg);
  this.name = 'ydn.error.ArgumentException';
};
goog.inherits(ydn.debug.error.ArgumentException, goog.debug.Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {goog.debug.Error}
 */
ydn.debug.error.TypeError = function(opt_msg) {
  goog.base(this, opt_msg);
  this.name = 'ydn.error.TypeError';
};
goog.inherits(ydn.debug.error.TypeError, goog.debug.Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {goog.debug.Error}
 */
ydn.debug.error.NotSupportedException = function(opt_msg) {
  goog.base(this, opt_msg);
  this.name = 'ydn.error.NotSupportedException';
};
goog.inherits(ydn.debug.error.NotSupportedException, goog.debug.Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {goog.debug.Error}
 */
ydn.debug.error.NotImplementedException = function(opt_msg) {
  goog.base(this, opt_msg);
  this.name = 'ydn.error.NotImplementedException';
};
goog.inherits(ydn.debug.error.NotImplementedException, goog.debug.Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {goog.debug.Error}
 */
ydn.debug.error.InvalidOperationException = function(opt_msg) {
  goog.base(this, opt_msg);
  this.name = 'ydn.error.InvalidOperationException';
};
goog.inherits(ydn.debug.error.InvalidOperationException, goog.debug.Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.debug.error.InternalError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.debug.error.InternalError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.InternalError';
};
goog.inherits(ydn.debug.error.InternalError, Error);


/**
 * Name of error.
 * @type {string}
 */
ydn.debug.error.InternalError.prototype.name = 'ydn.error.InternalError';


// Input 8
/**
 * @fileoverview About this file
 */

goog.provide('ydn.base');


/**
 * For JQuery output, deferred functions is slight different and adapt
 * the deferred to jquery style.
 * @define {boolean} true if target compile output is Jquery.
 */
ydn.base.JQUERY = false;
// Input 9
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Deferred object having progress callback.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.async.Deferred');
goog.require('goog.async.Deferred');
goog.require('ydn.debug.error.ArgumentException');
goog.require('ydn.base');



/**
 * A Deferred with progress event.
 *
 * @param {Function=} opt_onCancelFunction A function that will be called if the
 *     Deferred is canceled. If provided, this function runs before the
 *     Deferred is fired with a {@code CanceledError}.
 * @param {Object=} opt_defaultScope The default object context to call
 *     callbacks and errbacks in.
 * @constructor
 * @extends {goog.async.Deferred}
 * @struct
 * @suppress {checkStructDictInheritance} suppress closure-library code.
 */
ydn.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
  goog.base(this, opt_onCancelFunction, opt_defaultScope);
  /**
   * @type {!Array.<Array>} progress listener callbacks.
   */
  this.progbacks_ = [];
};
goog.inherits(ydn.async.Deferred, goog.async.Deferred);


/**
 * Register a callback function to be called for progress events.
 * @param {!function(this:T,?):?} fun The function to be called on progress.
 * @param {T=} opt_scope An optional scope to call the progback in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
ydn.async.Deferred.prototype.addProgback = function(fun, opt_scope) {
  this.progbacks_.push([fun, opt_scope]);
  return this;
};


/**
 * Notify to progress callback listers about the progress of the result.
 * @param {*=} opt_value The value.
 */
ydn.async.Deferred.prototype.notify = function(opt_value) {
  for (var i = 0; i < this.progbacks_.length; i++) {
    var progback = this.progbacks_[i][0];
    var scope = this.progbacks_[i][1];
    progback.call(scope, opt_value);
  }
};


/**
 * @inheritDoc
 */
ydn.async.Deferred.prototype.callback = function(opt_result) {
  this.progbacks_.length = 0;
  goog.base(this, 'callback', opt_result);
};


/**
 * @inheritDoc
 */
ydn.async.Deferred.prototype.errback = function(opt_result) {
  this.progbacks_.length = 0;
  goog.base(this, 'errback', opt_result);
};


/**
 * Export as JQuery compatible then method.
 *
 * @param {(function(this:T,?):?)|null} cb The function to be called on a
 *     successful result.
 * @param {(function(this:T,?):?)|null} eb The function to be called on an
 *     unsuccessful result.
 * @param {T=} opt_scope An optional scope to call the functions in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
ydn.async.Deferred.prototype.then = function(cb, eb, opt_scope) {
  // put a deferred adapter if callback return a thenable object.
  if (!!opt_scope && goog.isFunction(opt_scope)) {
    throw new ydn.debug.error.ArgumentException('progress callback is' +
        ' not supported');
  }
  var callback = cb;
  if (goog.isFunction(cb)) {
    callback = function(x) {
      var value = cb.call(opt_scope, x);
      if (goog.isObject(value) && goog.isFunction(value['then'])) {
        var df = new goog.async.Deferred();
        value['then'].call(opt_scope, function(x) {
          df.callback(x);
        }, function(e) {
          df.errback(e);
        });
        return df;
      } else {
        return value;
      }
    };
  }
  return this.addCallbacks(callback, eb, opt_scope);
};
// Input 10

goog.provide('ydn.base.exports');
goog.require('ydn.async.Deferred');

goog.exportProperty(goog.async.Deferred.prototype, 'done',
    goog.async.Deferred.prototype.addCallback);
goog.exportProperty(goog.async.Deferred.prototype, 'fail',
    goog.async.Deferred.prototype.addErrback);
goog.exportProperty(goog.async.Deferred.prototype, 'always',
    goog.async.Deferred.prototype.addBoth);
goog.exportProperty(ydn.async.Deferred.prototype, 'then',
    ydn.async.Deferred.prototype.then);


// Input 11
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for manipulating objects/maps/hashes.
 */

goog.provide('goog.object');


/**
 * Calls a function for each element in an object/map/hash.
 *
 * @param {Object.<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object.<K,V>):?} f The function to call
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the object) and the return value is ignored.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @template T,K,V
 */
goog.object.forEach = function(obj, f, opt_obj) {
  for (var key in obj) {
    f.call(opt_obj, obj[key], key, obj);
  }
};


/**
 * Calls a function for each element in an object/map/hash. If that call returns
 * true, adds the element to a new object.
 *
 * @param {Object.<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to call
 *     for every element. This
 *     function takes 3 arguments (the element, the index and the object)
 *     and should return a boolean. If the return value is true the
 *     element is added to the result object. If it is false the
 *     element is not included.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {!Object.<K,V>} a new object in which only elements that passed the
 *     test are present.
 * @template T,K,V
 */
goog.object.filter = function(obj, f, opt_obj) {
  var res = {};
  for (var key in obj) {
    if (f.call(opt_obj, obj[key], key, obj)) {
      res[key] = obj[key];
    }
  }
  return res;
};


/**
 * For every element in an object/map/hash calls a function and inserts the
 * result into a new object.
 *
 * @param {Object.<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object.<K,V>):R} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the object)
 *     and should return something. The result will be inserted
 *     into a new object.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {!Object.<K,R>} a new object with the results from f.
 * @template T,K,V,R
 */
goog.object.map = function(obj, f, opt_obj) {
  var res = {};
  for (var key in obj) {
    res[key] = f.call(opt_obj, obj[key], key, obj);
  }
  return res;
};


/**
 * Calls a function for each element in an object/map/hash. If any
 * call returns true, returns true (without checking the rest). If
 * all calls return false, returns false.
 *
 * @param {Object.<K,V>} obj The object to check.
 * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to
 *     call for every element. This function
 *     takes 3 arguments (the element, the index and the object) and should
 *     return a boolean.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {boolean} true if any element passes the test.
 * @template T,K,V
 */
goog.object.some = function(obj, f, opt_obj) {
  for (var key in obj) {
    if (f.call(opt_obj, obj[key], key, obj)) {
      return true;
    }
  }
  return false;
};


/**
 * Calls a function for each element in an object/map/hash. If
 * all calls return true, returns true. If any call returns false, returns
 * false at this point and does not continue to check the remaining elements.
 *
 * @param {Object.<K,V>} obj The object to check.
 * @param {?function(this:T,V,?,Object.<K,V>):boolean} f The function to
 *     call for every element. This function
 *     takes 3 arguments (the element, the index and the object) and should
 *     return a boolean.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {boolean} false if any element fails the test.
 * @template T,K,V
 */
goog.object.every = function(obj, f, opt_obj) {
  for (var key in obj) {
    if (!f.call(opt_obj, obj[key], key, obj)) {
      return false;
    }
  }
  return true;
};


/**
 * Returns the number of key-value pairs in the object map.
 *
 * @param {Object} obj The object for which to get the number of key-value
 *     pairs.
 * @return {number} The number of key-value pairs in the object map.
 */
goog.object.getCount = function(obj) {
  // JS1.5 has __count__ but it has been deprecated so it raises a warning...
  // in other words do not use. Also __count__ only includes the fields on the
  // actual object and not in the prototype chain.
  var rv = 0;
  for (var key in obj) {
    rv++;
  }
  return rv;
};


/**
 * Returns one key from the object map, if any exists.
 * For map literals the returned key will be the first one in most of the
 * browsers (a know exception is Konqueror).
 *
 * @param {Object} obj The object to pick a key from.
 * @return {string|undefined} The key or undefined if the object is empty.
 */
goog.object.getAnyKey = function(obj) {
  for (var key in obj) {
    return key;
  }
};


/**
 * Returns one value from the object map, if any exists.
 * For map literals the returned value will be the first one in most of the
 * browsers (a know exception is Konqueror).
 *
 * @param {Object.<K,V>} obj The object to pick a value from.
 * @return {V|undefined} The value or undefined if the object is empty.
 * @template K,V
 */
goog.object.getAnyValue = function(obj) {
  for (var key in obj) {
    return obj[key];
  }
};


/**
 * Whether the object/hash/map contains the given object as a value.
 * An alias for goog.object.containsValue(obj, val).
 *
 * @param {Object.<K,V>} obj The object in which to look for val.
 * @param {V} val The object for which to check.
 * @return {boolean} true if val is present.
 * @template K,V
 */
goog.object.contains = function(obj, val) {
  return goog.object.containsValue(obj, val);
};


/**
 * Returns the values of the object/map/hash.
 *
 * @param {Object.<K,V>} obj The object from which to get the values.
 * @return {!Array.<V>} The values in the object/map/hash.
 * @template K,V
 */
goog.object.getValues = function(obj) {
  var res = [];
  var i = 0;
  for (var key in obj) {
    res[i++] = obj[key];
  }
  return res;
};


/**
 * Returns the keys of the object/map/hash.
 *
 * @param {Object} obj The object from which to get the keys.
 * @return {!Array.<string>} Array of property keys.
 */
goog.object.getKeys = function(obj) {
  var res = [];
  var i = 0;
  for (var key in obj) {
    res[i++] = key;
  }
  return res;
};


/**
 * Get a value from an object multiple levels deep.  This is useful for
 * pulling values from deeply nested objects, such as JSON responses.
 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
 *
 * @param {!Object} obj An object to get the value from.  Can be array-like.
 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
 *     (as strings, or numbers, for array-like objects).  Can also be
 *     specified as a single array of keys.
 * @return {*} The resulting value.  If, at any point, the value for a key
 *     is undefined, returns undefined.
 */
goog.object.getValueByKeys = function(obj, var_args) {
  var isArrayLike = goog.isArrayLike(var_args);
  var keys = isArrayLike ? var_args : arguments;

  // Start with the 2nd parameter for the variable parameters syntax.
  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
    obj = obj[keys[i]];
    if (!goog.isDef(obj)) {
      break;
    }
  }

  return obj;
};


/**
 * Whether the object/map/hash contains the given key.
 *
 * @param {Object} obj The object in which to look for key.
 * @param {*} key The key for which to check.
 * @return {boolean} true If the map contains the key.
 */
goog.object.containsKey = function(obj, key) {
  return key in obj;
};


/**
 * Whether the object/map/hash contains the given value. This is O(n).
 *
 * @param {Object.<K,V>} obj The object in which to look for val.
 * @param {V} val The value for which to check.
 * @return {boolean} true If the map contains the value.
 * @template K,V
 */
goog.object.containsValue = function(obj, val) {
  for (var key in obj) {
    if (obj[key] == val) {
      return true;
    }
  }
  return false;
};


/**
 * Searches an object for an element that satisfies the given condition and
 * returns its key.
 * @param {Object.<K,V>} obj The object to search in.
 * @param {function(this:T,V,string,Object.<K,V>):boolean} f The
 *      function to call for every element. Takes 3 arguments (the value,
 *     the key and the object) and should return a boolean.
 * @param {T=} opt_this An optional "this" context for the function.
 * @return {string|undefined} The key of an element for which the function
 *     returns true or undefined if no such element is found.
 * @template T,K,V
 */
goog.object.findKey = function(obj, f, opt_this) {
  for (var key in obj) {
    if (f.call(opt_this, obj[key], key, obj)) {
      return key;
    }
  }
  return undefined;
};


/**
 * Searches an object for an element that satisfies the given condition and
 * returns its value.
 * @param {Object.<K,V>} obj The object to search in.
 * @param {function(this:T,V,string,Object.<K,V>):boolean} f The function
 *     to call for every element. Takes 3 arguments (the value, the key
 *     and the object) and should return a boolean.
 * @param {T=} opt_this An optional "this" context for the function.
 * @return {V} The value of an element for which the function returns true or
 *     undefined if no such element is found.
 * @template T,K,V
 */
goog.object.findValue = function(obj, f, opt_this) {
  var key = goog.object.findKey(obj, f, opt_this);
  return key && obj[key];
};


/**
 * Whether the object/map/hash is empty.
 *
 * @param {Object} obj The object to test.
 * @return {boolean} true if obj is empty.
 */
goog.object.isEmpty = function(obj) {
  for (var key in obj) {
    return false;
  }
  return true;
};


/**
 * Removes all key value pairs from the object/map/hash.
 *
 * @param {Object} obj The object to clear.
 */
goog.object.clear = function(obj) {
  for (var i in obj) {
    delete obj[i];
  }
};


/**
 * Removes a key-value pair based on the key.
 *
 * @param {Object} obj The object from which to remove the key.
 * @param {*} key The key to remove.
 * @return {boolean} Whether an element was removed.
 */
goog.object.remove = function(obj, key) {
  var rv;
  if ((rv = key in obj)) {
    delete obj[key];
  }
  return rv;
};


/**
 * Adds a key-value pair to the object. Throws an exception if the key is
 * already in use. Use set if you want to change an existing pair.
 *
 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} val The value to add.
 * @template K,V
 */
goog.object.add = function(obj, key, val) {
  if (key in obj) {
    throw Error('The object already contains the key "' + key + '"');
  }
  goog.object.set(obj, key, val);
};


/**
 * Returns the value for the given key.
 *
 * @param {Object.<K,V>} obj The object from which to get the value.
 * @param {string} key The key for which to get the value.
 * @param {R=} opt_val The value to return if no item is found for the given
 *     key (default is undefined).
 * @return {V|R|undefined} The value for the given key.
 * @template K,V,R
 */
goog.object.get = function(obj, key, opt_val) {
  if (key in obj) {
    return obj[key];
  }
  return opt_val;
};


/**
 * Adds a key-value pair to the object/map/hash.
 *
 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} value The value to add.
 * @template K,V
 */
goog.object.set = function(obj, key, value) {
  obj[key] = value;
};


/**
 * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
 *
 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} value The value to add if the key wasn't present.
 * @return {V} The value of the entry at the end of the function.
 * @template K,V
 */
goog.object.setIfUndefined = function(obj, key, value) {
  return key in obj ? obj[key] : (obj[key] = value);
};


/**
 * Does a flat clone of the object.
 *
 * @param {Object.<K,V>} obj Object to clone.
 * @return {!Object.<K,V>} Clone of the input object.
 * @template K,V
 */
goog.object.clone = function(obj) {
  // We cannot use the prototype trick because a lot of methods depend on where
  // the actual key is set.

  var res = {};
  for (var key in obj) {
    res[key] = obj[key];
  }
  return res;
  // We could also use goog.mixin but I wanted this to be independent from that.
};


/**
 * Clones a value. The input may be an Object, Array, or basic type. Objects and
 * arrays will be cloned recursively.
 *
 * WARNINGS:
 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
 * that refer to themselves will cause infinite recursion.
 *
 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
 * copies UIDs created by <code>getUid</code> into cloned results.
 *
 * @param {*} obj The value to clone.
 * @return {*} A clone of the input value.
 */
goog.object.unsafeClone = function(obj) {
  var type = goog.typeOf(obj);
  if (type == 'object' || type == 'array') {
    if (obj.clone) {
      return obj.clone();
    }
    var clone = type == 'array' ? [] : {};
    for (var key in obj) {
      clone[key] = goog.object.unsafeClone(obj[key]);
    }
    return clone;
  }

  return obj;
};


/**
 * Returns a new object in which all the keys and values are interchanged
 * (keys become values and values become keys). If multiple keys map to the
 * same value, the chosen transposed value is implementation-dependent.
 *
 * @param {Object} obj The object to transpose.
 * @return {!Object} The transposed object.
 */
goog.object.transpose = function(obj) {
  var transposed = {};
  for (var key in obj) {
    transposed[obj[key]] = key;
  }
  return transposed;
};


/**
 * The names of the fields that are defined on Object.prototype.
 * @type {Array.<string>}
 * @private
 */
goog.object.PROTOTYPE_FIELDS_ = [
  'constructor',
  'hasOwnProperty',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'toLocaleString',
  'toString',
  'valueOf'
];


/**
 * Extends an object with another object.
 * This operates 'in-place'; it does not create a new Object.
 *
 * Example:
 * var o = {};
 * goog.object.extend(o, {a: 0, b: 1});
 * o; // {a: 0, b: 1}
 * goog.object.extend(o, {c: 2});
 * o; // {a: 0, b: 1, c: 2}
 *
 * @param {Object} target  The object to modify.
 * @param {...Object} var_args The objects from which values will be copied.
 */
goog.object.extend = function(target, var_args) {
  var key, source;
  for (var i = 1; i < arguments.length; i++) {
    source = arguments[i];
    for (key in source) {
      target[key] = source[key];
    }

    // For IE the for-in-loop does not contain any properties that are not
    // enumerable on the prototype object (for example isPrototypeOf from
    // Object.prototype) and it will also not include 'replace' on objects that
    // extend String and change 'replace' (not that it is common for anyone to
    // extend anything except Object).

    for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
      key = goog.object.PROTOTYPE_FIELDS_[j];
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
};


/**
 * Creates a new object built from the key-value pairs provided as arguments.
 * @param {...*} var_args If only one argument is provided and it is an array
 *     then this is used as the arguments,  otherwise even arguments are used as
 *     the property names and odd arguments are used as the property values.
 * @return {!Object} The new object.
 * @throws {Error} If there are uneven number of arguments or there is only one
 *     non array argument.
 */
goog.object.create = function(var_args) {
  var argLength = arguments.length;
  if (argLength == 1 && goog.isArray(arguments[0])) {
    return goog.object.create.apply(null, arguments[0]);
  }

  if (argLength % 2) {
    throw Error('Uneven number of arguments');
  }

  var rv = {};
  for (var i = 0; i < argLength; i += 2) {
    rv[arguments[i]] = arguments[i + 1];
  }
  return rv;
};


/**
 * Creates a new object where the property names come from the arguments but
 * the value is always set to true
 * @param {...*} var_args If only one argument is provided and it is an array
 *     then this is used as the arguments,  otherwise the arguments are used
 *     as the property names.
 * @return {!Object} The new object.
 */
goog.object.createSet = function(var_args) {
  var argLength = arguments.length;
  if (argLength == 1 && goog.isArray(arguments[0])) {
    return goog.object.createSet.apply(null, arguments[0]);
  }

  var rv = {};
  for (var i = 0; i < argLength; i++) {
    rv[arguments[i]] = true;
  }
  return rv;
};


/**
 * Creates an immutable view of the underlying object, if the browser
 * supports immutable objects.
 *
 * In default mode, writes to this view will fail silently. In strict mode,
 * they will throw an error.
 *
 * @param {!Object.<K,V>} obj An object.
 * @return {!Object.<K,V>} An immutable view of that object, or the
 *     original object if this browser does not support immutables.
 * @template K,V
 */
goog.object.createImmutableView = function(obj) {
  var result = obj;
  if (Object.isFrozen && !Object.isFrozen(obj)) {
    result = Object.create(obj);
    Object.freeze(result);
  }
  return result;
};


/**
 * @param {!Object} obj An object.
 * @return {boolean} Whether this is an immutable view of the object.
 */
goog.object.isImmutableView = function(obj) {
  return !!Object.isFrozen && Object.isFrozen(obj);
};

// Input 12
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Generics method for collection-like classes and objects.
 *
 * @author arv@google.com (Erik Arvidsson)
 *
 * This file contains functions to work with collections. It supports using
 * Map, Set, Array and Object and other classes that implement collection-like
 * methods.
 */


goog.provide('goog.structs');

goog.require('goog.array');
goog.require('goog.object');


// We treat an object as a dictionary if it has getKeys or it is an object that
// isn't arrayLike.


/**
 * Returns the number of values in the collection-like object.
 * @param {Object} col The collection-like object.
 * @return {number} The number of values in the collection-like object.
 */
goog.structs.getCount = function(col) {
  if (typeof col.getCount == 'function') {
    return col.getCount();
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return col.length;
  }
  return goog.object.getCount(col);
};


/**
 * Returns the values of the collection-like object.
 * @param {Object} col The collection-like object.
 * @return {!Array} The values in the collection-like object.
 */
goog.structs.getValues = function(col) {
  if (typeof col.getValues == 'function') {
    return col.getValues();
  }
  if (goog.isString(col)) {
    return col.split('');
  }
  if (goog.isArrayLike(col)) {
    var rv = [];
    var l = col.length;
    for (var i = 0; i < l; i++) {
      rv.push(col[i]);
    }
    return rv;
  }
  return goog.object.getValues(col);
};


/**
 * Returns the keys of the collection. Some collections have no notion of
 * keys/indexes and this function will return undefined in those cases.
 * @param {Object} col The collection-like object.
 * @return {!Array|undefined} The keys in the collection.
 */
goog.structs.getKeys = function(col) {
  if (typeof col.getKeys == 'function') {
    return col.getKeys();
  }
  // if we have getValues but no getKeys we know this is a key-less collection
  if (typeof col.getValues == 'function') {
    return undefined;
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    var rv = [];
    var l = col.length;
    for (var i = 0; i < l; i++) {
      rv.push(i);
    }
    return rv;
  }

  return goog.object.getKeys(col);
};


/**
 * Whether the collection contains the given value. This is O(n) and uses
 * equals (==) to test the existence.
 * @param {Object} col The collection-like object.
 * @param {*} val The value to check for.
 * @return {boolean} True if the map contains the value.
 */
goog.structs.contains = function(col, val) {
  if (typeof col.contains == 'function') {
    return col.contains(val);
  }
  if (typeof col.containsValue == 'function') {
    return col.containsValue(val);
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.contains(/** @type {Array} */ (col), val);
  }
  return goog.object.containsValue(col, val);
};


/**
 * Whether the collection is empty.
 * @param {Object} col The collection-like object.
 * @return {boolean} True if empty.
 */
goog.structs.isEmpty = function(col) {
  if (typeof col.isEmpty == 'function') {
    return col.isEmpty();
  }

  // We do not use goog.string.isEmpty because here we treat the string as
  // collection and as such even whitespace matters

  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.isEmpty(/** @type {Array} */ (col));
  }
  return goog.object.isEmpty(col);
};


/**
 * Removes all the elements from the collection.
 * @param {Object} col The collection-like object.
 */
goog.structs.clear = function(col) {
  // NOTE(arv): This should not contain strings because strings are immutable
  if (typeof col.clear == 'function') {
    col.clear();
  } else if (goog.isArrayLike(col)) {
    goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
  } else {
    goog.object.clear(col);
  }
};


/**
 * Calls a function for each value in a collection. The function takes
 * three arguments; the value, the key and the collection.
 *
 * @param {S} col The collection-like object.
 * @param {function(this:T,?,?,S):?} f The function to call for every value.
 *     This function takes
 *     3 arguments (the value, the key or undefined if the collection has no
 *     notion of keys, and the collection) and the return value is irrelevant.
 * @param {T=} opt_obj The object to be used as the value of 'this'
 *     within {@code f}.
 * @template T,S
 */
goog.structs.forEach = function(col, f, opt_obj) {
  if (typeof col.forEach == 'function') {
    col.forEach(f, opt_obj);
  } else if (goog.isArrayLike(col) || goog.isString(col)) {
    goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
  } else {
    var keys = goog.structs.getKeys(col);
    var values = goog.structs.getValues(col);
    var l = values.length;
    for (var i = 0; i < l; i++) {
      f.call(opt_obj, values[i], keys && keys[i], col);
    }
  }
};


/**
 * Calls a function for every value in the collection. When a call returns true,
 * adds the value to a new collection (Array is returned by default).
 *
 * @param {S} col The collection-like object.
 * @param {function(this:T,?,?,S):boolean} f The function to call for every
 *     value. This function takes
 *     3 arguments (the value, the key or undefined if the collection has no
 *     notion of keys, and the collection) and should return a Boolean. If the
 *     return value is true the value is added to the result collection. If it
 *     is false the value is not included.
 * @param {T=} opt_obj The object to be used as the value of 'this'
 *     within {@code f}.
 * @return {!Object|!Array} A new collection where the passed values are
 *     present. If col is a key-less collection an array is returned.  If col
 *     has keys and values a plain old JS object is returned.
 * @template T,S
 */
goog.structs.filter = function(col, f, opt_obj) {
  if (typeof col.filter == 'function') {
    return col.filter(f, opt_obj);
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
  }

  var rv;
  var keys = goog.structs.getKeys(col);
  var values = goog.structs.getValues(col);
  var l = values.length;
  if (keys) {
    rv = {};
    for (var i = 0; i < l; i++) {
      if (f.call(opt_obj, values[i], keys[i], col)) {
        rv[keys[i]] = values[i];
      }
    }
  } else {
    // We should not use goog.array.filter here since we want to make sure that
    // the index is undefined as well as make sure that col is passed to the
    // function.
    rv = [];
    for (var i = 0; i < l; i++) {
      if (f.call(opt_obj, values[i], undefined, col)) {
        rv.push(values[i]);
      }
    }
  }
  return rv;
};


/**
 * Calls a function for every value in the collection and adds the result into a
 * new collection (defaults to creating a new Array).
 *
 * @param {S} col The collection-like object.
 * @param {function(this:T,?,?,S):V} f The function to call for every value.
 *     This function takes 3 arguments (the value, the key or undefined if the
 *     collection has no notion of keys, and the collection) and should return
 *     something. The result will be used as the value in the new collection.
 * @param {T=} opt_obj  The object to be used as the value of 'this'
 *     within {@code f}.
 * @return {!Object.<V>|!Array.<V>} A new collection with the new values.  If
 *     col is a key-less collection an array is returned.  If col has keys and
 *     values a plain old JS object is returned.
 * @template T,S,V
 */
goog.structs.map = function(col, f, opt_obj) {
  if (typeof col.map == 'function') {
    return col.map(f, opt_obj);
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
  }

  var rv;
  var keys = goog.structs.getKeys(col);
  var values = goog.structs.getValues(col);
  var l = values.length;
  if (keys) {
    rv = {};
    for (var i = 0; i < l; i++) {
      rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
    }
  } else {
    // We should not use goog.array.map here since we want to make sure that
    // the index is undefined as well as make sure that col is passed to the
    // function.
    rv = [];
    for (var i = 0; i < l; i++) {
      rv[i] = f.call(opt_obj, values[i], undefined, col);
    }
  }
  return rv;
};


/**
 * Calls f for each value in a collection. If any call returns true this returns
 * true (without checking the rest). If all returns false this returns false.
 *
 * @param {S} col The collection-like object.
 * @param {function(this:T,?,?,S):boolean} f The function to call for every
 *     value. This function takes 3 arguments (the value, the key or undefined
 *     if the collection has no notion of keys, and the collection) and should
 *     return a boolean.
 * @param {T=} opt_obj  The object to be used as the value of 'this'
 *     within {@code f}.
 * @return {boolean} True if any value passes the test.
 * @template T,S
 */
goog.structs.some = function(col, f, opt_obj) {
  if (typeof col.some == 'function') {
    return col.some(f, opt_obj);
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
  }
  var keys = goog.structs.getKeys(col);
  var values = goog.structs.getValues(col);
  var l = values.length;
  for (var i = 0; i < l; i++) {
    if (f.call(opt_obj, values[i], keys && keys[i], col)) {
      return true;
    }
  }
  return false;
};


/**
 * Calls f for each value in a collection. If all calls return true this return
 * true this returns true. If any returns false this returns false at this point
 *  and does not continue to check the remaining values.
 *
 * @param {S} col The collection-like object.
 * @param {function(this:T,?,?,S):boolean} f The function to call for every
 *     value. This function takes 3 arguments (the value, the key or
 *     undefined if the collection has no notion of keys, and the collection)
 *     and should return a boolean.
 * @param {T=} opt_obj  The object to be used as the value of 'this'
 *     within {@code f}.
 * @return {boolean} True if all key-value pairs pass the test.
 * @template T,S
 */
goog.structs.every = function(col, f, opt_obj) {
  if (typeof col.every == 'function') {
    return col.every(f, opt_obj);
  }
  if (goog.isArrayLike(col) || goog.isString(col)) {
    return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
  }
  var keys = goog.structs.getKeys(col);
  var values = goog.structs.getValues(col);
  var l = values.length;
  for (var i = 0; i < l; i++) {
    if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
      return false;
    }
  }
  return true;
};

// Input 13
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Defines the collection interface.
 *
 * @author nnaze@google.com (Nathan Naze)
 */

goog.provide('goog.structs.Collection');



/**
 * An interface for a collection of values.
 * @interface
 */
goog.structs.Collection = function() {};


/**
 * @param {*} value Value to add to the collection.
 */
goog.structs.Collection.prototype.add;


/**
 * @param {*} value Value to remove from the collection.
 */
goog.structs.Collection.prototype.remove;


/**
 * @param {*} value Value to find in the tree.
 * @return {boolean} Whether the collection contains the specified value.
 */
goog.structs.Collection.prototype.contains;


/**
 * @return {number} The number of values stored in the collection.
 */
goog.structs.Collection.prototype.getCount;


// Input 14
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Python style iteration utilities.
 * @author arv@google.com (Erik Arvidsson)
 */


goog.provide('goog.iter');
goog.provide('goog.iter.Iterator');
goog.provide('goog.iter.StopIteration');

goog.require('goog.array');
goog.require('goog.asserts');


// TODO(nnaze): Add more functions from Python's itertools.
// http://docs.python.org/library/itertools.html


/**
 * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
 */
goog.iter.Iterable;


// For script engines that already support iterators.
if ('StopIteration' in goog.global) {
  /**
   * Singleton Error object that is used to terminate iterations.
   * @type {Error}
   */
  goog.iter.StopIteration = goog.global['StopIteration'];
} else {
  /**
   * Singleton Error object that is used to terminate iterations.
   * @type {Error}
   * @suppress {duplicate}
   */
  goog.iter.StopIteration = Error('StopIteration');
}



/**
 * Class/interface for iterators.  An iterator needs to implement a {@code next}
 * method and it needs to throw a {@code goog.iter.StopIteration} when the
 * iteration passes beyond the end.  Iterators have no {@code hasNext} method.
 * It is recommended to always use the helper functions to iterate over the
 * iterator or in case you are only targeting JavaScript 1.7 for in loops.
 * @constructor
 */
goog.iter.Iterator = function() {};


/**
 * Returns the next value of the iteration.  This will throw the object
 * {@see goog.iter#StopIteration} when the iteration passes the end.
 * @return {*} Any object or value.
 */
goog.iter.Iterator.prototype.next = function() {
  throw goog.iter.StopIteration;
};


/**
 * Returns the {@code Iterator} object itself.  This is used to implement
 * the iterator protocol in JavaScript 1.7
 * @param {boolean=} opt_keys  Whether to return the keys or values. Default is
 *     to only return the values.  This is being used by the for-in loop (true)
 *     and the for-each-in loop (false).  Even though the param gives a hint
 *     about what the iterator will return there is no guarantee that it will
 *     return the keys when true is passed.
 * @return {!goog.iter.Iterator} The object itself.
 */
goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
  return this;
};


/**
 * Returns an iterator that knows how to iterate over the values in the object.
 * @param {goog.iter.Iterable} iterable  If the object is an iterator it
 *     will be returned as is.  If the object has a {@code __iterator__} method
 *     that will be called to get the value iterator.  If the object is an
 *     array-like object we create an iterator for that.
 * @return {!goog.iter.Iterator} An iterator that knows how to iterate over the
 *     values in {@code iterable}.
 */
goog.iter.toIterator = function(iterable) {
  if (iterable instanceof goog.iter.Iterator) {
    return iterable;
  }
  if (typeof iterable.__iterator__ == 'function') {
    return iterable.__iterator__(false);
  }
  if (goog.isArrayLike(iterable)) {
    var i = 0;
    var newIter = new goog.iter.Iterator;
    newIter.next = function() {
      while (true) {
        if (i >= iterable.length) {
          throw goog.iter.StopIteration;
        }
        // Don't include deleted elements.
        if (!(i in iterable)) {
          i++;
          continue;
        }
        return iterable[i++];
      }
    };
    return newIter;
  }


  // TODO(arv): Should we fall back on goog.structs.getValues()?
  throw Error('Not implemented');
};


/**
 * Calls a function for each element in the iterator with the element of the
 * iterator passed as argument.
 *
 * @param {goog.iter.Iterable} iterable  The iterator to iterate
 *     over.  If the iterable is an object {@code toIterator} will be called on
 *     it.
* @param {function(this:T,?,?,?):?} f  The function to call for every
 *     element.  This function
 *     takes 3 arguments (the element, undefined, and the iterator) and the
 *     return value is irrelevant.  The reason for passing undefined as the
 *     second argument is so that the same function can be used in
 *     {@see goog.array#forEach} as well as others.
 * @param {T=} opt_obj  The object to be used as the value of 'this' within
 *     {@code f}.
 * @template T
 */
goog.iter.forEach = function(iterable, f, opt_obj) {
  if (goog.isArrayLike(iterable)) {
    /** @preserveTry */
    try {
      // NOTES: this passes the index number to the second parameter
      // of the callback contrary to the documentation above.
      goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
                         opt_obj);
    } catch (ex) {
      if (ex !== goog.iter.StopIteration) {
        throw ex;
      }
    }
  } else {
    iterable = goog.iter.toIterator(iterable);
    /** @preserveTry */
    try {
      while (true) {
        f.call(opt_obj, iterable.next(), undefined, iterable);
      }
    } catch (ex) {
      if (ex !== goog.iter.StopIteration) {
        throw ex;
      }
    }
  }
};


/**
 * Calls a function for every element in the iterator, and if the function
 * returns true adds the element to a new iterator.
 *
 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
 *     every element. This function
 *     takes 3 arguments (the element, undefined, and the iterator) and should
 *     return a boolean.  If the return value is true the element will be
 *     included  in the returned iteror.  If it is false the element is not
 *     included.
 * @param {T=} opt_obj The object to be used as the value of 'this' within
 *     {@code f}.
 * @return {!goog.iter.Iterator} A new iterator in which only elements that
 *     passed the test are present.
 * @template T
 */
goog.iter.filter = function(iterable, f, opt_obj) {
  var iterator = goog.iter.toIterator(iterable);
  var newIter = new goog.iter.Iterator;
  newIter.next = function() {
    while (true) {
      var val = iterator.next();
      if (f.call(opt_obj, val, undefined, iterator)) {
        return val;
      }
    }
  };
  return newIter;
};


/**
 * Creates a new iterator that returns the values in a range.  This function
 * can take 1, 2 or 3 arguments:
 * <pre>
 * range(5) same as range(0, 5, 1)
 * range(2, 5) same as range(2, 5, 1)
 * </pre>
 *
 * @param {number} startOrStop  The stop value if only one argument is provided.
 *     The start value if 2 or more arguments are provided.  If only one
 *     argument is used the start value is 0.
 * @param {number=} opt_stop  The stop value.  If left out then the first
 *     argument is used as the stop value.
 * @param {number=} opt_step  The number to increment with between each call to
 *     next.  This can be negative.
 * @return {!goog.iter.Iterator} A new iterator that returns the values in the
 *     range.
 */
goog.iter.range = function(startOrStop, opt_stop, opt_step) {
  var start = 0;
  var stop = startOrStop;
  var step = opt_step || 1;
  if (arguments.length > 1) {
    start = startOrStop;
    stop = opt_stop;
  }
  if (step == 0) {
    throw Error('Range step argument must not be zero');
  }

  var newIter = new goog.iter.Iterator;
  newIter.next = function() {
    if (step > 0 && start >= stop || step < 0 && start <= stop) {
      throw goog.iter.StopIteration;
    }
    var rv = start;
    start += step;
    return rv;
  };
  return newIter;
};


/**
 * Joins the values in a iterator with a delimiter.
 * @param {goog.iter.Iterable} iterable  The iterator to get the values from.
 * @param {string} deliminator  The text to put between the values.
 * @return {string} The joined value string.
 */
goog.iter.join = function(iterable, deliminator) {
  return goog.iter.toArray(iterable).join(deliminator);
};


/**
 * For every element in the iterator call a function and return a new iterator
 * with that value.
 *
 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
 * @param {function(this:T,?,undefined,?):?} f The function to call for every
 *     element.  This function
 *     takes 3 arguments (the element, undefined, and the iterator) and should
 *     return a new value.
 * @param {T=} opt_obj The object to be used as the value of 'this' within
 *     {@code f}.
 * @return {!goog.iter.Iterator} A new iterator that returns the results of
 *     applying the function to each element in the original iterator.
 * @template T
 */
goog.iter.map = function(iterable, f, opt_obj) {
  var iterator = goog.iter.toIterator(iterable);
  var newIter = new goog.iter.Iterator;
  newIter.next = function() {
    while (true) {
      var val = iterator.next();
      return f.call(opt_obj, val, undefined, iterator);
    }
  };
  return newIter;
};


/**
 * Passes every element of an iterator into a function and accumulates the
 * result.
 *
 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
 * @param {function(this:T,V,?):V} f The function to call for every
 *     element. This function takes 2 arguments (the function's previous result
 *     or the initial value, and the value of the current element).
 *     function(previousValue, currentElement) : newValue.
 * @param {V} val The initial value to pass into the function on the first call.
 * @param {T=} opt_obj  The object to be used as the value of 'this'
 *     within f.
 * @return {V} Result of evaluating f repeatedly across the values of
 *     the iterator.
 * @template T,V
 */
goog.iter.reduce = function(iterable, f, val, opt_obj) {
  var rval = val;
  goog.iter.forEach(iterable, function(val) {
    rval = f.call(opt_obj, rval, val);
  });
  return rval;
};


/**
 * Goes through the values in the iterator. Calls f for each these and if any of
 * them returns true, this returns true (without checking the rest). If all
 * return false this will return false.
 *
 * @param {goog.iter.Iterable} iterable  The iterator object.
 * @param {function(this:T,?,undefined,?):boolean} f  The function to call for
 *     every value. This function
 *     takes 3 arguments (the value, undefined, and the iterator) and should
 *     return a boolean.
 * @param {T=} opt_obj The object to be used as the value of 'this' within
 *     {@code f}.
 * @return {boolean} true if any value passes the test.
 * @template T
 */
goog.iter.some = function(iterable, f, opt_obj) {
  iterable = goog.iter.toIterator(iterable);
  /** @preserveTry */
  try {
    while (true) {
      if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
        return true;
      }
    }
  } catch (ex) {
    if (ex !== goog.iter.StopIteration) {
      throw ex;
    }
  }
  return false;
};


/**
 * Goes through the values in the iterator. Calls f for each these and if any of
 * them returns false this returns false (without checking the rest). If all
 * return true this will return true.
 *
 * @param {goog.iter.Iterable} iterable  The iterator object.
 * @param {function(this:T,?,undefined,?):boolean} f  The function to call for
 *     every value. This function
 *     takes 3 arguments (the value, undefined, and the iterator) and should
 *     return a boolean.
 * @param {T=} opt_obj The object to be used as the value of 'this' within
 *     {@code f}.
 * @return {boolean} true if every value passes the test.
 * @template T
 */
goog.iter.every = function(iterable, f, opt_obj) {
  iterable = goog.iter.toIterator(iterable);
  /** @preserveTry */
  try {
    while (true) {
      if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
        return false;
      }
    }
  } catch (ex) {
    if (ex !== goog.iter.StopIteration) {
      throw ex;
    }
  }
  return true;
};


/**
 * Takes zero or more iterators and returns one iterator that will iterate over
 * them in the order chained.
 * @param {...goog.iter.Iterator} var_args  Any number of iterator objects.
 * @return {!goog.iter.Iterator} Returns a new iterator that will iterate over
 *     all the given iterators' contents.
 */
goog.iter.chain = function(var_args) {
  var args = arguments;
  var length = args.length;
  var i = 0;
  var newIter = new goog.iter.Iterator;

  /**
   * @return {*} The next item in the iteration.
   * @this {goog.iter.Iterator}
   */
  newIter.next = function() {
    /** @preserveTry */
    try {
      if (i >= length) {
        throw goog.iter.StopIteration;
      }
      var current = goog.iter.toIterator(args[i]);
      return current.next();
    } catch (ex) {
      if (ex !== goog.iter.StopIteration || i >= length) {
        throw ex;
      } else {
        // In case we got a StopIteration increment counter and try again.
        i++;
        return this.next();
      }
    }
  };

  return newIter;
};


/**
 * Builds a new iterator that iterates over the original, but skips elements as
 * long as a supplied function returns true.
 * @param {goog.iter.Iterable} iterable  The iterator object.
 * @param {function(this:T,?,undefined,?):boolean} f  The function to call for
 *     every value. This function
 *     takes 3 arguments (the value, undefined, and the iterator) and should
 *     return a boolean.
 * @param {T=} opt_obj The object to be used as the value of 'this' within
 *     {@code f}.
 * @return {!goog.iter.Iterator} A new iterator that drops elements from the
 *     original iterator as long as {@code f} is true.
 * @template T
 */
goog.iter.dropWhile = function(iterable, f, opt_obj) {
  var iterator = goog.iter.toIterator(iterable);
  var newIter = new goog.iter.Iterator;
  var dropping = true;
  newIter.next = function() {
    while (true) {
      var val = iterator.next();
      if (dropping && f.call(opt_obj, val, undefined, iterator)) {
        continue;
      } else {
        dropping = false;
      }
      return val;
    }
  };
  return newIter;
};


/**
 * Builds a new iterator that iterates over the original, but only as long as a
 * supplied function returns true.
 * @param {goog.iter.Iterable} iterable  The iterator object.
 * @param {function(this:T,?,undefined,?):boolean} f  The function to call for
 *     every value. This function
 *     takes 3 arguments (the value, undefined, and the iterator) and should
 *     return a boolean.
 * @param {T=} opt_obj This is used as the 'this' object in f when called.
 * @return {!goog.iter.Iterator} A new iterator that keeps elements in the
 *     original iterator as long as the function is true.
 * @template T
 */
goog.iter.takeWhile = function(iterable, f, opt_obj) {
  var iterator = goog.iter.toIterator(iterable);
  var newIter = new goog.iter.Iterator;
  var taking = true;
  newIter.next = function() {
    while (true) {
      if (taking) {
        var val = iterator.next();
        if (f.call(opt_obj, val, undefined, iterator)) {
          return val;
        } else {
          taking = false;
        }
      } else {
        throw goog.iter.StopIteration;
      }
    }
  };
  return newIter;
};


/**
 * Converts the iterator to an array
 * @param {goog.iter.Iterable} iterable  The iterator to convert to an array.
 * @return {!Array} An array of the elements the iterator iterates over.
 */
goog.iter.toArray = function(iterable) {
  // Fast path for array-like.
  if (goog.isArrayLike(iterable)) {
    return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
  }
  iterable = goog.iter.toIterator(iterable);
  var array = [];
  goog.iter.forEach(iterable, function(val) {
    array.push(val);
  });
  return array;
};


/**
 * Iterates over 2 iterators and returns true if they contain the same sequence
 * of elements and have the same length.
 * @param {goog.iter.Iterable} iterable1  The first iterable object.
 * @param {goog.iter.Iterable} iterable2  The second iterable object.
 * @return {boolean} true if the iterators contain the same sequence of
 *     elements and have the same length.
 */
goog.iter.equals = function(iterable1, iterable2) {
  iterable1 = goog.iter.toIterator(iterable1);
  iterable2 = goog.iter.toIterator(iterable2);
  var b1, b2;
  /** @preserveTry */
  try {
    while (true) {
      b1 = b2 = false;
      var val1 = iterable1.next();
      b1 = true;
      var val2 = iterable2.next();
      b2 = true;
      if (val1 != val2) {
        return false;
      }
    }
  } catch (ex) {
    if (ex !== goog.iter.StopIteration) {
      throw ex;
    } else {
      if (b1 && !b2) {
        // iterable1 done but iterable2 is not done.
        return false;
      }
      if (!b2) {
        /** @preserveTry */
        try {
          // iterable2 not done?
          val2 = iterable2.next();
          // iterable2 not done but iterable1 is done
          return false;
        } catch (ex1) {
          if (ex1 !== goog.iter.StopIteration) {
            throw ex1;
          }
          // iterable2 done as well... They are equal
          return true;
        }
      }
    }
  }
  return false;
};


/**
 * Advances the iterator to the next position, returning the given default value
 * instead of throwing an exception if the iterator has no more entries.
 * @param {goog.iter.Iterable} iterable The iterable object.
 * @param {*} defaultValue The value to return if the iterator is empty.
 * @return {*} The next item in the iteration, or defaultValue if the iterator
 *     was empty.
 */
goog.iter.nextOrValue = function(iterable, defaultValue) {
  try {
    return goog.iter.toIterator(iterable).next();
  } catch (e) {
    if (e != goog.iter.StopIteration) {
      throw e;
    }
    return defaultValue;
  }
};


/**
 * Cartesian product of zero or more sets.  Gives an iterator that gives every
 * combination of one element chosen from each set.  For example,
 * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
 * @see http://docs.python.org/library/itertools.html#itertools.product
 * @param {...!goog.array.ArrayLike.<*>} var_args Zero or more sets, as arrays.
 * @return {!goog.iter.Iterator} An iterator that gives each n-tuple (as an
 *     array).
 */
goog.iter.product = function(var_args) {
  var someArrayEmpty = goog.array.some(arguments, function(arr) {
    return !arr.length;
  });

  // An empty set in a cartesian product gives an empty set.
  if (someArrayEmpty || !arguments.length) {
    return new goog.iter.Iterator();
  }

  var iter = new goog.iter.Iterator();
  var arrays = arguments;

  // The first indicies are [0, 0, ...]
  var indicies = goog.array.repeat(0, arrays.length);

  iter.next = function() {

    if (indicies) {
      var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
        return arrays[arrayIndex][valueIndex];
      });

      // Generate the next-largest indicies for the next call.
      // Increase the rightmost index. If it goes over, increase the next
      // rightmost (like carry-over addition).
      for (var i = indicies.length - 1; i >= 0; i--) {
        // Assertion prevents compiler warning below.
        goog.asserts.assert(indicies);
        if (indicies[i] < arrays[i].length - 1) {
          indicies[i]++;
          break;
        }

        // We're at the last indicies (the last element of every array), so
        // the iteration is over on the next call.
        if (i == 0) {
          indicies = null;
          break;
        }
        // Reset the index in this column and loop back to increment the
        // next one.
        indicies[i] = 0;
      }
      return retVal;
    }

    throw goog.iter.StopIteration;
  };

  return iter;
};


/**
 * Create an iterator to cycle over the iterable's elements indefinitely.
 * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
 * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
 * @param {!goog.iter.Iterable} iterable The iterable object.
 * @return {!goog.iter.Iterator} An iterator that iterates indefinitely over
 * the values in {@code iterable}.
 */
goog.iter.cycle = function(iterable) {

  var baseIterator = goog.iter.toIterator(iterable);

  // We maintain a cache to store the iterable elements as we iterate
  // over them. The cache is used to return elements once we have
  // iterated over the iterable once.
  var cache = [];
  var cacheIndex = 0;

  var iter = new goog.iter.Iterator();

  // This flag is set after the iterable is iterated over once
  var useCache = false;

  iter.next = function() {
    var returnElement = null;

    // Pull elements off the original iterator if not using cache
    if (!useCache) {
      try {
        // Return the element from the iterable
        returnElement = baseIterator.next();
        cache.push(returnElement);
        return returnElement;
      } catch (e) {
        // If an exception other than StopIteration is thrown
        // or if there are no elements to iterate over (the iterable was empty)
        // throw an exception
        if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
          throw e;
        }
        // set useCache to true after we know that a 'StopIteration' exception
        // was thrown and the cache is not empty (to handle the 'empty iterable'
        // use case)
        useCache = true;
      }
    }

    returnElement = cache[cacheIndex];
    cacheIndex = (cacheIndex + 1) % cache.length;

    return returnElement;
  };

  return iter;
};

// Input 15
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Datastructure: Hash Map.
 *
 * @author arv@google.com (Erik Arvidsson)
 * @author jonp@google.com (Jon Perlow) Optimized for IE6
 *
 * This file contains an implementation of a Map structure. It implements a lot
 * of the methods used in goog.structs so those functions work on hashes. This
 * is best suited for complex key types. For simple keys such as numbers and
 * strings, and where special names like __proto__ are not a concern, consider
 * using the lighter-weight utilities in goog.object.
 */


goog.provide('goog.structs.Map');

goog.require('goog.iter.Iterator');
goog.require('goog.iter.StopIteration');
goog.require('goog.object');
goog.require('goog.structs');



/**
 * Class for Hash Map datastructure.
 * @param {*=} opt_map Map or Object to initialize the map with.
 * @param {...*} var_args If 2 or more arguments are present then they
 *     will be used as key-value pairs.
 * @constructor
 */
goog.structs.Map = function(opt_map, var_args) {

  /**
   * Underlying JS object used to implement the map.
   * @type {!Object}
   * @private
   */
  this.map_ = {};

  /**
   * An array of keys. This is necessary for two reasons:
   *   1. Iterating the keys using for (var key in this.map_) allocates an
   *      object for every key in IE which is really bad for IE6 GC perf.
   *   2. Without a side data structure, we would need to escape all the keys
   *      as that would be the only way we could tell during iteration if the
   *      key was an internal key or a property of the object.
   *
   * This array can contain deleted keys so it's necessary to check the map
   * as well to see if the key is still in the map (this doesn't require a
   * memory allocation in IE).
   * @type {!Array.<string>}
   * @private
   */
  this.keys_ = [];

  var argLength = arguments.length;

  if (argLength > 1) {
    if (argLength % 2) {
      throw Error('Uneven number of arguments');
    }
    for (var i = 0; i < argLength; i += 2) {
      this.set(arguments[i], arguments[i + 1]);
    }
  } else if (opt_map) {
    this.addAll(/** @type {Object} */ (opt_map));
  }
};


/**
 * The number of key value pairs in the map.
 * @private
 * @type {number}
 */
goog.structs.Map.prototype.count_ = 0;


/**
 * Version used to detect changes while iterating.
 * @private
 * @type {number}
 */
goog.structs.Map.prototype.version_ = 0;


/**
 * @return {number} The number of key-value pairs in the map.
 */
goog.structs.Map.prototype.getCount = function() {
  return this.count_;
};


/**
 * Returns the values of the map.
 * @return {!Array} The values in the map.
 */
goog.structs.Map.prototype.getValues = function() {
  this.cleanupKeysArray_();

  var rv = [];
  for (var i = 0; i < this.keys_.length; i++) {
    var key = this.keys_[i];
    rv.push(this.map_[key]);
  }
  return rv;
};


/**
 * Returns the keys of the map.
 * @return {!Array.<string>} Array of string values.
 */
goog.structs.Map.prototype.getKeys = function() {
  this.cleanupKeysArray_();
  return /** @type {!Array.<string>} */ (this.keys_.concat());
};


/**
 * Whether the map contains the given key.
 * @param {*} key The key to check for.
 * @return {boolean} Whether the map contains the key.
 */
goog.structs.Map.prototype.containsKey = function(key) {
  return goog.structs.Map.hasKey_(this.map_, key);
};


/**
 * Whether the map contains the given value. This is O(n).
 * @param {*} val The value to check for.
 * @return {boolean} Whether the map contains the value.
 */
goog.structs.Map.prototype.containsValue = function(val) {
  for (var i = 0; i < this.keys_.length; i++) {
    var key = this.keys_[i];
    if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
      return true;
    }
  }
  return false;
};


/**
 * Whether this map is equal to the argument map.
 * @param {goog.structs.Map} otherMap The map against which to test equality.
 * @param {function(?, ?) : boolean=} opt_equalityFn Optional equality function
 *     to test equality of values. If not specified, this will test whether
 *     the values contained in each map are identical objects.
 * @return {boolean} Whether the maps are equal.
 */
goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
  if (this === otherMap) {
    return true;
  }

  if (this.count_ != otherMap.getCount()) {
    return false;
  }

  var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;

  this.cleanupKeysArray_();
  for (var key, i = 0; key = this.keys_[i]; i++) {
    if (!equalityFn(this.get(key), otherMap.get(key))) {
      return false;
    }
  }

  return true;
};


/**
 * Default equality test for values.
 * @param {*} a The first value.
 * @param {*} b The second value.
 * @return {boolean} Whether a and b reference the same object.
 */
goog.structs.Map.defaultEquals = function(a, b) {
  return a === b;
};


/**
 * @return {boolean} Whether the map is empty.
 */
goog.structs.Map.prototype.isEmpty = function() {
  return this.count_ == 0;
};


/**
 * Removes all key-value pairs from the map.
 */
goog.structs.Map.prototype.clear = function() {
  this.map_ = {};
  this.keys_.length = 0;
  this.count_ = 0;
  this.version_ = 0;
};


/**
 * Removes a key-value pair based on the key. This is O(logN) amortized due to
 * updating the keys array whenever the count becomes half the size of the keys
 * in the keys array.
 * @param {*} key  The key to remove.
 * @return {boolean} Whether object was removed.
 */
goog.structs.Map.prototype.remove = function(key) {
  if (goog.structs.Map.hasKey_(this.map_, key)) {
    delete this.map_[key];
    this.count_--;
    this.version_++;

    // clean up the keys array if the threshhold is hit
    if (this.keys_.length > 2 * this.count_) {
      this.cleanupKeysArray_();
    }

    return true;
  }
  return false;
};


/**
 * Cleans up the temp keys array by removing entries that are no longer in the
 * map.
 * @private
 */
goog.structs.Map.prototype.cleanupKeysArray_ = function() {
  if (this.count_ != this.keys_.length) {
    // First remove keys that are no longer in the map.
    var srcIndex = 0;
    var destIndex = 0;
    while (srcIndex < this.keys_.length) {
      var key = this.keys_[srcIndex];
      if (goog.structs.Map.hasKey_(this.map_, key)) {
        this.keys_[destIndex++] = key;
      }
      srcIndex++;
    }
    this.keys_.length = destIndex;
  }

  if (this.count_ != this.keys_.length) {
    // If the count still isn't correct, that means we have duplicates. This can
    // happen when the same key is added and removed multiple times. Now we have
    // to allocate one extra Object to remove the duplicates. This could have
    // been done in the first pass, but in the common case, we can avoid
    // allocating an extra object by only doing this when necessary.
    var seen = {};
    var srcIndex = 0;
    var destIndex = 0;
    while (srcIndex < this.keys_.length) {
      var key = this.keys_[srcIndex];
      if (!(goog.structs.Map.hasKey_(seen, key))) {
        this.keys_[destIndex++] = key;
        seen[key] = 1;
      }
      srcIndex++;
    }
    this.keys_.length = destIndex;
  }
};


/**
 * Returns the value for the given key.  If the key is not found and the default
 * value is not given this will return {@code undefined}.
 * @param {*} key The key to get the value for.
 * @param {*=} opt_val The value to return if no item is found for the given
 *     key, defaults to undefined.
 * @return {*} The value for the given key.
 */
goog.structs.Map.prototype.get = function(key, opt_val) {
  if (goog.structs.Map.hasKey_(this.map_, key)) {
    return this.map_[key];
  }
  return opt_val;
};


/**
 * Adds a key-value pair to the map.
 * @param {*} key The key.
 * @param {*} value The value to add.
 * @return {*} Some subclasses return a value.
 */
goog.structs.Map.prototype.set = function(key, value) {
  if (!(goog.structs.Map.hasKey_(this.map_, key))) {
    this.count_++;
    this.keys_.push(key);
    // Only change the version if we add a new key.
    this.version_++;
  }
  this.map_[key] = value;
};


/**
 * Adds multiple key-value pairs from another goog.structs.Map or Object.
 * @param {Object} map  Object containing the data to add.
 */
goog.structs.Map.prototype.addAll = function(map) {
  var keys, values;
  if (map instanceof goog.structs.Map) {
    keys = map.getKeys();
    values = map.getValues();
  } else {
    keys = goog.object.getKeys(map);
    values = goog.object.getValues(map);
  }
  // we could use goog.array.forEach here but I don't want to introduce that
  // dependency just for this.
  for (var i = 0; i < keys.length; i++) {
    this.set(keys[i], values[i]);
  }
};


/**
 * Clones a map and returns a new map.
 * @return {!goog.structs.Map} A new map with the same key-value pairs.
 */
goog.structs.Map.prototype.clone = function() {
  return new goog.structs.Map(this);
};


/**
 * Returns a new map in which all the keys and values are interchanged
 * (keys become values and values become keys). If multiple keys map to the
 * same value, the chosen transposed value is implementation-dependent.
 *
 * It acts very similarly to {goog.object.transpose(Object)}.
 *
 * @return {!goog.structs.Map} The transposed map.
 */
goog.structs.Map.prototype.transpose = function() {
  var transposed = new goog.structs.Map();
  for (var i = 0; i < this.keys_.length; i++) {
    var key = this.keys_[i];
    var value = this.map_[key];
    transposed.set(value, key);
  }

  return transposed;
};


/**
 * @return {!Object} Object representation of the map.
 */
goog.structs.Map.prototype.toObject = function() {
  this.cleanupKeysArray_();
  var obj = {};
  for (var i = 0; i < this.keys_.length; i++) {
    var key = this.keys_[i];
    obj[key] = this.map_[key];
  }
  return obj;
};


/**
 * Returns an iterator that iterates over the keys in the map.  Removal of keys
 * while iterating might have undesired side effects.
 * @return {!goog.iter.Iterator} An iterator over the keys in the map.
 */
goog.structs.Map.prototype.getKeyIterator = function() {
  return this.__iterator__(true);
};


/**
 * Returns an iterator that iterates over the values in the map.  Removal of
 * keys while iterating might have undesired side effects.
 * @return {!goog.iter.Iterator} An iterator over the values in the map.
 */
goog.structs.Map.prototype.getValueIterator = function() {
  return this.__iterator__(false);
};


/**
 * Returns an iterator that iterates over the values or the keys in the map.
 * This throws an exception if the map was mutated since the iterator was
 * created.
 * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
 *     over the values.  The default value is false.
 * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
 */
goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
  // Clean up keys to minimize the risk of iterating over dead keys.
  this.cleanupKeysArray_();

  var i = 0;
  var keys = this.keys_;
  var map = this.map_;
  var version = this.version_;
  var selfObj = this;

  var newIter = new goog.iter.Iterator;
  newIter.next = function() {
    while (true) {
      if (version != selfObj.version_) {
        throw Error('The map has changed since the iterator was created');
      }
      if (i >= keys.length) {
        throw goog.iter.StopIteration;
      }
      var key = keys[i++];
      return opt_keys ? key : map[key];
    }
  };
  return newIter;
};


/**
 * Safe way to test for hasOwnProperty.  It even allows testing for
 * 'hasOwnProperty'.
 * @param {Object} obj The object to test for presence of the given key.
 * @param {*} key The key to check for.
 * @return {boolean} Whether the object has the key.
 * @private
 */
goog.structs.Map.hasKey_ = function(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
};

// Input 16
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Datastructure: Set.
 *
 * @author arv@google.com (Erik Arvidsson)
 * @author pallosp@google.com (Peter Pallos)
 *
 * This class implements a set data structure. Adding and removing is O(1). It
 * supports both object and primitive values. Be careful because you can add
 * both 1 and new Number(1), because these are not the same. You can even add
 * multiple new Number(1) because these are not equal.
 */


goog.provide('goog.structs.Set');

goog.require('goog.structs');
goog.require('goog.structs.Collection');
goog.require('goog.structs.Map');



/**
 * A set that can contain both primitives and objects.  Adding and removing
 * elements is O(1).  Primitives are treated as identical if they have the same
 * type and convert to the same string.  Objects are treated as identical only
 * if they are references to the same object.  WARNING: A goog.structs.Set can
 * contain both 1 and (new Number(1)), because they are not the same.  WARNING:
 * Adding (new Number(1)) twice will yield two distinct elements, because they
 * are two different objects.  WARNING: Any object that is added to a
 * goog.structs.Set will be modified!  Because goog.getUid() is used to
 * identify objects, every object in the set will be mutated.
 * @param {Array|Object=} opt_values Initial values to start with.
 * @constructor
 * @implements {goog.structs.Collection}
 */
goog.structs.Set = function(opt_values) {
  this.map_ = new goog.structs.Map;
  if (opt_values) {
    this.addAll(opt_values);
  }
};


/**
 * Obtains a unique key for an element of the set.  Primitives will yield the
 * same key if they have the same type and convert to the same string.  Object
 * references will yield the same key only if they refer to the same object.
 * @param {*} val Object or primitive value to get a key for.
 * @return {string} A unique key for this value/object.
 * @private
 */
goog.structs.Set.getKey_ = function(val) {
  var type = typeof val;
  if (type == 'object' && val || type == 'function') {
    return 'o' + goog.getUid(/** @type {Object} */ (val));
  } else {
    return type.substr(0, 1) + val;
  }
};


/**
 * @return {number} The number of elements in the set.
 * @override
 */
goog.structs.Set.prototype.getCount = function() {
  return this.map_.getCount();
};


/**
 * Add a primitive or an object to the set.
 * @param {*} element The primitive or object to add.
 * @override
 */
goog.structs.Set.prototype.add = function(element) {
  this.map_.set(goog.structs.Set.getKey_(element), element);
};


/**
 * Adds all the values in the given collection to this set.
 * @param {Array|Object} col A collection containing the elements to add.
 */
goog.structs.Set.prototype.addAll = function(col) {
  var values = goog.structs.getValues(col);
  var l = values.length;
  for (var i = 0; i < l; i++) {
    this.add(values[i]);
  }
};


/**
 * Removes all values in the given collection from this set.
 * @param {Array|Object} col A collection containing the elements to remove.
 */
goog.structs.Set.prototype.removeAll = function(col) {
  var values = goog.structs.getValues(col);
  var l = values.length;
  for (var i = 0; i < l; i++) {
    this.remove(values[i]);
  }
};


/**
 * Removes the given element from this set.
 * @param {*} element The primitive or object to remove.
 * @return {boolean} Whether the element was found and removed.
 * @override
 */
goog.structs.Set.prototype.remove = function(element) {
  return this.map_.remove(goog.structs.Set.getKey_(element));
};


/**
 * Removes all elements from this set.
 */
goog.structs.Set.prototype.clear = function() {
  this.map_.clear();
};


/**
 * Tests whether this set is empty.
 * @return {boolean} True if there are no elements in this set.
 */
goog.structs.Set.prototype.isEmpty = function() {
  return this.map_.isEmpty();
};


/**
 * Tests whether this set contains the given element.
 * @param {*} element The primitive or object to test for.
 * @return {boolean} True if this set contains the given element.
 * @override
 */
goog.structs.Set.prototype.contains = function(element) {
  return this.map_.containsKey(goog.structs.Set.getKey_(element));
};


/**
 * Tests whether this set contains all the values in a given collection.
 * Repeated elements in the collection are ignored, e.g.  (new
 * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
 * @param {Object} col A collection-like object.
 * @return {boolean} True if the set contains all elements.
 */
goog.structs.Set.prototype.containsAll = function(col) {
  return goog.structs.every(col, this.contains, this);
};


/**
 * Finds all values that are present in both this set and the given collection.
 * @param {Array|Object} col A collection.
 * @return {!goog.structs.Set} A new set containing all the values (primitives
 *     or objects) present in both this set and the given collection.
 */
goog.structs.Set.prototype.intersection = function(col) {
  var result = new goog.structs.Set();

  var values = goog.structs.getValues(col);
  for (var i = 0; i < values.length; i++) {
    var value = values[i];
    if (this.contains(value)) {
      result.add(value);
    }
  }

  return result;
};


/**
 * Finds all values that are present in this set and not in the given
 * collection.
 * @param {Array|Object} col A collection.
 * @return {!goog.structs.Set} A new set containing all the values
 *     (primitives or objects) present in this set but not in the given
 *     collection.
 */
goog.structs.Set.prototype.difference = function(col) {
  var result = this.clone();
  result.removeAll(col);
  return result;
};


/**
 * Returns an array containing all the elements in this set.
 * @return {!Array} An array containing all the elements in this set.
 */
goog.structs.Set.prototype.getValues = function() {
  return this.map_.getValues();
};


/**
 * Creates a shallow clone of this set.
 * @return {!goog.structs.Set} A new set containing all the same elements as
 *     this set.
 */
goog.structs.Set.prototype.clone = function() {
  return new goog.structs.Set(this);
};


/**
 * Tests whether the given collection consists of the same elements as this set,
 * regardless of order, without repetition.  Primitives are treated as equal if
 * they have the same type and convert to the same string; objects are treated
 * as equal if they are references to the same object.  This operation is O(n).
 * @param {Object} col A collection.
 * @return {boolean} True if the given collection consists of the same elements
 *     as this set, regardless of order, without repetition.
 */
goog.structs.Set.prototype.equals = function(col) {
  return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
};


/**
 * Tests whether the given collection contains all the elements in this set.
 * Primitives are treated as equal if they have the same type and convert to the
 * same string; objects are treated as equal if they are references to the same
 * object.  This operation is O(n).
 * @param {Object} col A collection.
 * @return {boolean} True if this set is a subset of the given collection.
 */
goog.structs.Set.prototype.isSubsetOf = function(col) {
  var colCount = goog.structs.getCount(col);
  if (this.getCount() > colCount) {
    return false;
  }
  // TODO(user) Find the minimal collection size where the conversion makes
  // the contains() method faster.
  if (!(col instanceof goog.structs.Set) && colCount > 5) {
    // Convert to a goog.structs.Set so that goog.structs.contains runs in
    // O(1) time instead of O(n) time.
    col = new goog.structs.Set(col);
  }
  return goog.structs.every(this, function(value) {
    return goog.structs.contains(col, value);
  });
};


/**
 * Returns an iterator that iterates over the elements in this set.
 * @param {boolean=} opt_keys This argument is ignored.
 * @return {!goog.iter.Iterator} An iterator over the elements in this set.
 */
goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
  return this.map_.__iterator__(false);
};

// Input 17
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Rendering engine detection.
 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
 * For information on the browser brand (such as Safari versus Chrome), see
 * goog.userAgent.product.
 * @see ../demos/useragent.html
 */

goog.provide('goog.userAgent');

goog.require('goog.string');


/**
 * @define {boolean} Whether we know at compile-time that the browser is IE.
 */
goog.define('goog.userAgent.ASSUME_IE', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is GECKO.
 */
goog.define('goog.userAgent.ASSUME_GECKO', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
 */
goog.define('goog.userAgent.ASSUME_WEBKIT', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is a
 *     mobile device running WebKit e.g. iPhone or Android.
 */
goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is OPERA.
 */
goog.define('goog.userAgent.ASSUME_OPERA', false);


/**
 * @define {boolean} Whether the
 *     {@code goog.userAgent.isVersionOrHigher}
 *     function will return true for any version.
 */
goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);


/**
 * Whether we know the browser engine at compile-time.
 * @type {boolean}
 * @private
 */
goog.userAgent.BROWSER_KNOWN_ =
    goog.userAgent.ASSUME_IE ||
    goog.userAgent.ASSUME_GECKO ||
    goog.userAgent.ASSUME_MOBILE_WEBKIT ||
    goog.userAgent.ASSUME_WEBKIT ||
    goog.userAgent.ASSUME_OPERA;


/**
 * Returns the userAgent string for the current browser.
 * Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a
 * navigator object off the global scope.  In that case we return null.
 *
 * @return {?string} The userAgent string or null if there is none.
 */
goog.userAgent.getUserAgentString = function() {
  return goog.global['navigator'] ? goog.global['navigator'].userAgent : null;
};


/**
 * @return {Object} The native navigator object.
 */
goog.userAgent.getNavigator = function() {
  // Need a local navigator reference instead of using the global one,
  // to avoid the rare case where they reference different objects.
  // (in a WorkerPool, for example).
  return goog.global['navigator'];
};


/**
 * Initializer for goog.userAgent.
 *
 * This is a named function so that it can be stripped via the jscompiler
 * option for stripping types.
 * @private
 */
goog.userAgent.init_ = function() {
  /**
   * Whether the user agent string denotes Opera.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedOpera_ = false;

  /**
   * Whether the user agent string denotes Internet Explorer. This includes
   * other browsers using Trident as its rendering engine. For example AOL
   * and Netscape 8
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedIe_ = false;

  /**
   * Whether the user agent string denotes WebKit. WebKit is the rendering
   * engine that Safari, Android and others use.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedWebkit_ = false;

  /**
   * Whether the user agent string denotes a mobile device.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedMobile_ = false;

  /**
   * Whether the user agent string denotes Gecko. Gecko is the rendering
   * engine used by Mozilla, Mozilla Firefox, Camino and many more.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedGecko_ = false;

  var ua;
  if (!goog.userAgent.BROWSER_KNOWN_ &&
      (ua = goog.userAgent.getUserAgentString())) {
    var navigator = goog.userAgent.getNavigator();
    goog.userAgent.detectedOpera_ = ua.indexOf('Opera') == 0;
    goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ &&
        ua.indexOf('MSIE') != -1;
    goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ &&
        ua.indexOf('WebKit') != -1;
    // WebKit also gives navigator.product string equal to 'Gecko'.
    goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ &&
        ua.indexOf('Mobile') != -1;
    goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ &&
        !goog.userAgent.detectedWebkit_ && navigator.product == 'Gecko';
  }
};


if (!goog.userAgent.BROWSER_KNOWN_) {
  goog.userAgent.init_();
}


/**
 * Whether the user agent is Opera.
 * @type {boolean}
 */
goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_;


/**
 * Whether the user agent is Internet Explorer. This includes other browsers
 * using Trident as its rendering engine. For example AOL and Netscape 8
 * @type {boolean}
 */
goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_;


/**
 * Whether the user agent is Gecko. Gecko is the rendering engine used by
 * Mozilla, Mozilla Firefox, Camino and many more.
 * @type {boolean}
 */
goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_GECKO :
    goog.userAgent.detectedGecko_;


/**
 * Whether the user agent is WebKit. WebKit is the rendering engine that
 * Safari, Android and others use.
 * @type {boolean}
 */
goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
    goog.userAgent.detectedWebkit_;


/**
 * Whether the user agent is running on a mobile device.
 * @type {boolean}
 */
goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
                        goog.userAgent.detectedMobile_;


/**
 * Used while transitioning code to use WEBKIT instead.
 * @type {boolean}
 * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
 * TODO(nicksantos): Delete this from goog.userAgent.
 */
goog.userAgent.SAFARI = goog.userAgent.WEBKIT;


/**
 * @return {string} the platform (operating system) the user agent is running
 *     on. Default to empty string because navigator.platform may not be defined
 *     (on Rhino, for example).
 * @private
 */
goog.userAgent.determinePlatform_ = function() {
  var navigator = goog.userAgent.getNavigator();
  return navigator && navigator.platform || '';
};


/**
 * The platform (operating system) the user agent is running on. Default to
 * empty string because navigator.platform may not be defined (on Rhino, for
 * example).
 * @type {string}
 */
goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();


/**
 * @define {boolean} Whether the user agent is running on a Macintosh operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_MAC', false);


/**
 * @define {boolean} Whether the user agent is running on a Windows operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_WINDOWS', false);


/**
 * @define {boolean} Whether the user agent is running on a Linux operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_LINUX', false);


/**
 * @define {boolean} Whether the user agent is running on a X11 windowing
 *     system.
 */
goog.define('goog.userAgent.ASSUME_X11', false);


/**
 * @define {boolean} Whether the user agent is running on Android.
 */
goog.define('goog.userAgent.ASSUME_ANDROID', false);


/**
 * @define {boolean} Whether the user agent is running on an iPhone.
 */
goog.define('goog.userAgent.ASSUME_IPHONE', false);


/**
 * @define {boolean} Whether the user agent is running on an iPad.
 */
goog.define('goog.userAgent.ASSUME_IPAD', false);


/**
 * @type {boolean}
 * @private
 */
goog.userAgent.PLATFORM_KNOWN_ =
    goog.userAgent.ASSUME_MAC ||
    goog.userAgent.ASSUME_WINDOWS ||
    goog.userAgent.ASSUME_LINUX ||
    goog.userAgent.ASSUME_X11 ||
    goog.userAgent.ASSUME_ANDROID ||
    goog.userAgent.ASSUME_IPHONE ||
    goog.userAgent.ASSUME_IPAD;


/**
 * Initialize the goog.userAgent constants that define which platform the user
 * agent is running on.
 * @private
 */
goog.userAgent.initPlatform_ = function() {
  /**
   * Whether the user agent is running on a Macintosh operating system.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM,
      'Mac');

  /**
   * Whether the user agent is running on a Windows operating system.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedWindows_ = goog.string.contains(
      goog.userAgent.PLATFORM, 'Win');

  /**
   * Whether the user agent is running on a Linux operating system.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM,
      'Linux');

  /**
   * Whether the user agent is running on a X11 windowing system.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() &&
      goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '',
          'X11');

  // Need user agent string for Android/IOS detection
  var ua = goog.userAgent.getUserAgentString();

  /**
   * Whether the user agent is running on Android.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedAndroid_ = !!ua && ua.indexOf('Android') >= 0;

  /**
   * Whether the user agent is running on an iPhone.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedIPhone_ = !!ua && ua.indexOf('iPhone') >= 0;

  /**
   * Whether the user agent is running on an iPad.
   * @type {boolean}
   * @private
   */
  goog.userAgent.detectedIPad_ = !!ua && ua.indexOf('iPad') >= 0;
};


if (!goog.userAgent.PLATFORM_KNOWN_) {
  goog.userAgent.initPlatform_();
}


/**
 * Whether the user agent is running on a Macintosh operating system.
 * @type {boolean}
 */
goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;


/**
 * Whether the user agent is running on a Windows operating system.
 * @type {boolean}
 */
goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;


/**
 * Whether the user agent is running on a Linux operating system.
 * @type {boolean}
 */
goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;


/**
 * Whether the user agent is running on a X11 windowing system.
 * @type {boolean}
 */
goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;


/**
 * Whether the user agent is running on Android.
 * @type {boolean}
 */
goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_;


/**
 * Whether the user agent is running on an iPhone.
 * @type {boolean}
 */
goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_;


/**
 * Whether the user agent is running on an iPad.
 * @type {boolean}
 */
goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_;


/**
 * @return {string} The string that describes the version number of the user
 *     agent.
 * @private
 */
goog.userAgent.determineVersion_ = function() {
  // All browsers have different ways to detect the version and they all have
  // different naming schemes.

  // version is a string rather than a number because it may contain 'b', 'a',
  // and so on.
  var version = '', re;

  if (goog.userAgent.OPERA && goog.global['opera']) {
    var operaVersion = goog.global['opera'].version;
    version = typeof operaVersion == 'function' ? operaVersion() : operaVersion;
  } else {
    if (goog.userAgent.GECKO) {
      re = /rv\:([^\);]+)(\)|;)/;
    } else if (goog.userAgent.IE) {
      re = /MSIE\s+([^\);]+)(\)|;)/;
    } else if (goog.userAgent.WEBKIT) {
      // WebKit/125.4
      re = /WebKit\/(\S+)/;
    }
    if (re) {
      var arr = re.exec(goog.userAgent.getUserAgentString());
      version = arr ? arr[1] : '';
    }
  }
  if (goog.userAgent.IE) {
    // IE9 can be in document mode 9 but be reporting an inconsistent user agent
    // version.  If it is identifying as a version lower than 9 we take the
    // documentMode as the version instead.  IE8 has similar behavior.
    // It is recommended to set the X-UA-Compatible header to ensure that IE9
    // uses documentMode 9.
    var docMode = goog.userAgent.getDocumentMode_();
    if (docMode > parseFloat(version)) {
      return String(docMode);
    }
  }
  return version;
};


/**
 * @return {number|undefined} Returns the document mode (for testing).
 * @private
 */
goog.userAgent.getDocumentMode_ = function() {
  // NOTE(user): goog.userAgent may be used in context where there is no DOM.
  var doc = goog.global['document'];
  return doc ? doc['documentMode'] : undefined;
};


/**
 * The version of the user agent. This is a string because it might contain
 * 'b' (as in beta) as well as multiple dots.
 * @type {string}
 */
goog.userAgent.VERSION = goog.userAgent.determineVersion_();


/**
 * Compares two version numbers.
 *
 * @param {string} v1 Version of first item.
 * @param {string} v2 Version of second item.
 *
 * @return {number}  1 if first argument is higher
 *                   0 if arguments are equal
 *                  -1 if second argument is higher.
 * @deprecated Use goog.string.compareVersions.
 */
goog.userAgent.compare = function(v1, v2) {
  return goog.string.compareVersions(v1, v2);
};


/**
 * Cache for {@link goog.userAgent.isVersionOrHigher}.
 * Calls to compareVersions are surprisingly expensive and, as a browser's
 * version number is unlikely to change during a session, we cache the results.
 * @const
 * @private
 */
goog.userAgent.isVersionOrHigherCache_ = {};


/**
 * Whether the user agent version is higher or the same as the given version.
 * NOTE: When checking the version numbers for Firefox and Safari, be sure to
 * use the engine's version, not the browser's version number.  For example,
 * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
 * Opera and Internet Explorer versions match the product release number.<br>
 * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
 *     Webkit</a>
 * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
 *
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the user agent version is higher or the same as
 *     the given version.
 */
goog.userAgent.isVersionOrHigher = function(version) {
  return goog.userAgent.ASSUME_ANY_VERSION ||
      goog.userAgent.isVersionOrHigherCache_[version] ||
      (goog.userAgent.isVersionOrHigherCache_[version] =
          goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
};


/**
 * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the user agent version is higher or the same as
 *     the given version.
 * @deprecated Use goog.userAgent.isVersionOrHigher().
 */
goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;


/**
 * Whether the IE effective document mode is higher or the same as the given
 * document mode version.
 * NOTE: Only for IE, return false for another browser.
 *
 * @param {number} documentMode The document mode version to check.
 * @return {boolean} Whether the IE effective document mode is higher or the
 *     same as the given version.
 */
goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
  return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
};


/**
 * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
 * @param {number} version The version to check.
 * @return {boolean} Whether the IE effective document mode is higher or the
 *      same as the given version.
 */
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;


/**
 * For IE version < 7, documentMode is undefined, so attempt to use the
 * CSS1Compat property to see if we are in standards mode. If we are in
 * standards mode, treat the browser version as the document mode. Otherwise,
 * IE is emulating version 5.
 * @type {number|undefined}
 * @const
 */
goog.userAgent.DOCUMENT_MODE = (function() {
  var doc = goog.global['document'];
  if (!doc || !goog.userAgent.IE) {
    return undefined;
  }
  var mode = goog.userAgent.getDocumentMode_();
  return mode || (doc['compatMode'] == 'CSS1Compat' ?
      parseInt(goog.userAgent.VERSION, 10) : 5);
})();

// Input 18
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Logging and debugging utilities.
 *
 * @see ../demos/debug.html
 */

goog.provide('goog.debug');

goog.require('goog.array');
goog.require('goog.string');
goog.require('goog.structs.Set');
goog.require('goog.userAgent');


/** @define {boolean} Whether logging should be enabled. */
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);


/**
 * Catches onerror events fired by windows and similar objects.
 * @param {function(Object)} logFunc The function to call with the error
 *    information.
 * @param {boolean=} opt_cancel Whether to stop the error from reaching the
 *    browser.
 * @param {Object=} opt_target Object that fires onerror events.
 */
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
  var target = opt_target || goog.global;
  var oldErrorHandler = target.onerror;
  var retVal = !!opt_cancel;

  // Chrome interprets onerror return value backwards (http://crbug.com/92062)
  // until it was fixed in webkit revision r94061 (Webkit 535.3). This
  // workaround still needs to be skipped in Safari after the webkit change
  // gets pushed out in Safari.
  // See https://bugs.webkit.org/show_bug.cgi?id=67119
  if (goog.userAgent.WEBKIT &&
      !goog.userAgent.isVersionOrHigher('535.3')) {
    retVal = !retVal;
  }
  target.onerror = function(message, url, line) {
    if (oldErrorHandler) {
      oldErrorHandler(message, url, line);
    }
    logFunc({
      message: message,
      fileName: url,
      line: line
    });
    return retVal;
  };
};


/**
 * Creates a string representing an object and all its properties.
 * @param {Object|null|undefined} obj Object to expose.
 * @param {boolean=} opt_showFn Show the functions as well as the properties,
 *     default is false.
 * @return {string} The string representation of {@code obj}.
 */
goog.debug.expose = function(obj, opt_showFn) {
  if (typeof obj == 'undefined') {
    return 'undefined';
  }
  if (obj == null) {
    return 'NULL';
  }
  var str = [];

  for (var x in obj) {
    if (!opt_showFn && goog.isFunction(obj[x])) {
      continue;
    }
    var s = x + ' = ';
    /** @preserveTry */
    try {
      s += obj[x];
    } catch (e) {
      s += '*** ' + e + ' ***';
    }
    str.push(s);
  }
  return str.join('\n');
};


/**
 * Creates a string representing a given primitive or object, and for an
 * object, all its properties and nested objects.  WARNING: If an object is
 * given, it and all its nested objects will be modified.  To detect reference
 * cycles, this method identifies objects using goog.getUid() which mutates the
 * object.
 * @param {*} obj Object to expose.
 * @param {boolean=} opt_showFn Also show properties that are functions (by
 *     default, functions are omitted).
 * @return {string} A string representation of {@code obj}.
 */
goog.debug.deepExpose = function(obj, opt_showFn) {
  var previous = new goog.structs.Set();
  var str = [];

  var helper = function(obj, space) {
    var nestspace = space + '  ';

    var indentMultiline = function(str) {
      return str.replace(/\n/g, '\n' + space);
    };

    /** @preserveTry */
    try {
      if (!goog.isDef(obj)) {
        str.push('undefined');
      } else if (goog.isNull(obj)) {
        str.push('NULL');
      } else if (goog.isString(obj)) {
        str.push('"' + indentMultiline(obj) + '"');
      } else if (goog.isFunction(obj)) {
        str.push(indentMultiline(String(obj)));
      } else if (goog.isObject(obj)) {
        if (previous.contains(obj)) {
          // TODO(user): This is a bug; it falsely detects non-loops as loops
          // when the reference tree contains two references to the same object.
          str.push('*** reference loop detected ***');
        } else {
          previous.add(obj);
          str.push('{');
          for (var x in obj) {
            if (!opt_showFn && goog.isFunction(obj[x])) {
              continue;
            }
            str.push('\n');
            str.push(nestspace);
            str.push(x + ' = ');
            helper(obj[x], nestspace);
          }
          str.push('\n' + space + '}');
        }
      } else {
        str.push(obj);
      }
    } catch (e) {
      str.push('*** ' + e + ' ***');
    }
  };

  helper(obj, '');
  return str.join('');
};


/**
 * Recursively outputs a nested array as a string.
 * @param {Array} arr The array.
 * @return {string} String representing nested array.
 */
goog.debug.exposeArray = function(arr) {
  var str = [];
  for (var i = 0; i < arr.length; i++) {
    if (goog.isArray(arr[i])) {
      str.push(goog.debug.exposeArray(arr[i]));
    } else {
      str.push(arr[i]);
    }
  }
  return '[ ' + str.join(', ') + ' ]';
};


/**
 * Exposes an exception that has been caught by a try...catch and outputs the
 * error with a stack trace.
 * @param {Object} err Error object or string.
 * @param {Function=} opt_fn Optional function to start stack trace from.
 * @return {string} Details of exception.
 */
goog.debug.exposeException = function(err, opt_fn) {
  /** @preserveTry */
  try {
    var e = goog.debug.normalizeErrorObject(err);

    // Create the error message
    var error = 'Message: ' + goog.string.htmlEscape(e.message) +
        '\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' +
        e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
        goog.string.htmlEscape(e.stack + '-> ') +
        '[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape(
            goog.debug.getStacktrace(opt_fn) + '-> ');
    return error;
  } catch (e2) {
    return 'Exception trying to expose exception! You win, we lose. ' + e2;
  }
};


/**
 * Normalizes the error/exception object between browsers.
 * @param {Object} err Raw error object.
 * @return {Object} Normalized error object.
 */
goog.debug.normalizeErrorObject = function(err) {
  var href = goog.getObjectByName('window.location.href');
  if (goog.isString(err)) {
    return {
      'message': err,
      'name': 'Unknown error',
      'lineNumber': 'Not available',
      'fileName': href,
      'stack': 'Not available'
    };
  }

  var lineNumber, fileName;
  var threwError = false;

  try {
    lineNumber = err.lineNumber || err.line || 'Not available';
  } catch (e) {
    // Firefox 2 sometimes throws an error when accessing 'lineNumber':
    // Message: Permission denied to get property UnnamedClass.lineNumber
    lineNumber = 'Not available';
    threwError = true;
  }

  try {
    fileName = err.fileName || err.filename || err.sourceURL ||
        // $googDebugFname may be set before a call to eval to set the filename
        // that the eval is supposed to present.
        goog.global['$googDebugFname'] || href;
  } catch (e) {
    // Firefox 2 may also throw an error when accessing 'filename'.
    fileName = 'Not available';
    threwError = true;
  }

  // The IE Error object contains only the name and the message.
  // The Safari Error object uses the line and sourceURL fields.
  if (threwError || !err.lineNumber || !err.fileName || !err.stack) {
    return {
      'message': err.message,
      'name': err.name,
      'lineNumber': lineNumber,
      'fileName': fileName,
      'stack': err.stack || 'Not available'
    };
  }

  // Standards error object
  return err;
};


/**
 * Converts an object to an Error if it's a String,
 * adds a stacktrace if there isn't one,
 * and optionally adds an extra message.
 * @param {Error|string} err  the original thrown object or string.
 * @param {string=} opt_message  optional additional message to add to the
 *     error.
 * @return {Error} If err is a string, it is used to create a new Error,
 *     which is enhanced and returned.  Otherwise err itself is enhanced
 *     and returned.
 */
goog.debug.enhanceError = function(err, opt_message) {
  var error = typeof err == 'string' ? Error(err) : err;
  if (!error.stack) {
    error.stack = goog.debug.getStacktrace(arguments.callee.caller);
  }
  if (opt_message) {
    // find the first unoccupied 'messageX' property
    var x = 0;
    while (error['message' + x]) {
      ++x;
    }
    error['message' + x] = String(opt_message);
  }
  return error;
};


/**
 * Gets the current stack trace. Simple and iterative - doesn't worry about
 * catching circular references or getting the args.
 * @param {number=} opt_depth Optional maximum depth to trace back to.
 * @return {string} A string with the function names of all functions in the
 *     stack, separated by \n.
 */
goog.debug.getStacktraceSimple = function(opt_depth) {
  var sb = [];
  var fn = arguments.callee.caller;
  var depth = 0;

  while (fn && (!opt_depth || depth < opt_depth)) {
    sb.push(goog.debug.getFunctionName(fn));
    sb.push('()\n');
    /** @preserveTry */
    try {
      fn = fn.caller;
    } catch (e) {
      sb.push('[exception trying to get caller]\n');
      break;
    }
    depth++;
    if (depth >= goog.debug.MAX_STACK_DEPTH) {
      sb.push('[...long stack...]');
      break;
    }
  }
  if (opt_depth && depth >= opt_depth) {
    sb.push('[...reached max depth limit...]');
  } else {
    sb.push('[end]');
  }

  return sb.join('');
};


/**
 * Max length of stack to try and output
 * @type {number}
 */
goog.debug.MAX_STACK_DEPTH = 50;


/**
 * Gets the current stack trace, either starting from the caller or starting
 * from a specified function that's currently on the call stack.
 * @param {Function=} opt_fn Optional function to start getting the trace from.
 *     If not provided, defaults to the function that called this.
 * @return {string} Stack trace.
 */
goog.debug.getStacktrace = function(opt_fn) {
  return goog.debug.getStacktraceHelper_(opt_fn || arguments.callee.caller, []);
};


/**
 * Private helper for getStacktrace().
 * @param {Function} fn Function to start getting the trace from.
 * @param {Array} visited List of functions visited so far.
 * @return {string} Stack trace starting from function fn.
 * @private
 */
goog.debug.getStacktraceHelper_ = function(fn, visited) {
  var sb = [];

  // Circular reference, certain functions like bind seem to cause a recursive
  // loop so we need to catch circular references
  if (goog.array.contains(visited, fn)) {
    sb.push('[...circular reference...]');

  // Traverse the call stack until function not found or max depth is reached
  } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
    sb.push(goog.debug.getFunctionName(fn) + '(');
    var args = fn.arguments;
    for (var i = 0; i < args.length; i++) {
      if (i > 0) {
        sb.push(', ');
      }
      var argDesc;
      var arg = args[i];
      switch (typeof arg) {
        case 'object':
          argDesc = arg ? 'object' : 'null';
          break;

        case 'string':
          argDesc = arg;
          break;

        case 'number':
          argDesc = String(arg);
          break;

        case 'boolean':
          argDesc = arg ? 'true' : 'false';
          break;

        case 'function':
          argDesc = goog.debug.getFunctionName(arg);
          argDesc = argDesc ? argDesc : '[fn]';
          break;

        case 'undefined':
        default:
          argDesc = typeof arg;
          break;
      }

      if (argDesc.length > 40) {
        argDesc = argDesc.substr(0, 40) + '...';
      }
      sb.push(argDesc);
    }
    visited.push(fn);
    sb.push(')\n');
    /** @preserveTry */
    try {
      sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
    } catch (e) {
      sb.push('[exception trying to get caller]\n');
    }

  } else if (fn) {
    sb.push('[...long stack...]');
  } else {
    sb.push('[end]');
  }
  return sb.join('');
};


/**
 * Set a custom function name resolver.
 * @param {function(Function): string} resolver Resolves functions to their
 *     names.
 */
goog.debug.setFunctionResolver = function(resolver) {
  goog.debug.fnNameResolver_ = resolver;
};


/**
 * Gets a function name
 * @param {Function} fn Function to get name of.
 * @return {string} Function's name.
 */
goog.debug.getFunctionName = function(fn) {
  if (goog.debug.fnNameCache_[fn]) {
    return goog.debug.fnNameCache_[fn];
  }
  if (goog.debug.fnNameResolver_) {
    var name = goog.debug.fnNameResolver_(fn);
    if (name) {
      goog.debug.fnNameCache_[fn] = name;
      return name;
    }
  }

  // Heuristically determine function name based on code.
  var functionSource = String(fn);
  if (!goog.debug.fnNameCache_[functionSource]) {
    var matches = /function ([^\(]+)/.exec(functionSource);
    if (matches) {
      var method = matches[1];
      goog.debug.fnNameCache_[functionSource] = method;
    } else {
      goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
    }
  }

  return goog.debug.fnNameCache_[functionSource];
};


/**
 * Makes whitespace visible by replacing it with printable characters.
 * This is useful in finding diffrences between the expected and the actual
 * output strings of a testcase.
 * @param {string} string whose whitespace needs to be made visible.
 * @return {string} string whose whitespace is made visible.
 */
goog.debug.makeWhitespaceVisible = function(string) {
  return string.replace(/ /g, '[_]')
      .replace(/\f/g, '[f]')
      .replace(/\n/g, '[n]\n')
      .replace(/\r/g, '[r]')
      .replace(/\t/g, '[t]');
};


/**
 * Hash map for storing function names that have already been looked up.
 * @type {Object}
 * @private
 */
goog.debug.fnNameCache_ = {};


/**
 * Resolves functions to their names.  Resolved function names will be cached.
 * @type {function(Function):string}
 * @private
 */
goog.debug.fnNameResolver_;

// Input 19
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the LogRecord class. Please minimize
 * dependencies this file has on other closure classes as any dependency it
 * takes won't be able to use the logging infrastructure.
 *
 */

goog.provide('goog.debug.LogRecord');



/**
 * LogRecord objects are used to pass logging requests between
 * the logging framework and individual log Handlers.
 * @constructor
 * @param {goog.debug.Logger.Level} level One of the level identifiers.
 * @param {string} msg The string message.
 * @param {string} loggerName The name of the source logger.
 * @param {number=} opt_time Time this log record was created if other than now.
 *     If 0, we use #goog.now.
 * @param {number=} opt_sequenceNumber Sequence number of this log record. This
 *     should only be passed in when restoring a log record from persistence.
 */
goog.debug.LogRecord = function(level, msg, loggerName,
    opt_time, opt_sequenceNumber) {
  this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
};


/**
 * Time the LogRecord was created.
 * @type {number}
 * @private
 */
goog.debug.LogRecord.prototype.time_;


/**
 * Level of the LogRecord
 * @type {goog.debug.Logger.Level}
 * @private
 */
goog.debug.LogRecord.prototype.level_;


/**
 * Message associated with the record
 * @type {string}
 * @private
 */
goog.debug.LogRecord.prototype.msg_;


/**
 * Name of the logger that created the record.
 * @type {string}
 * @private
 */
goog.debug.LogRecord.prototype.loggerName_;


/**
 * Sequence number for the LogRecord. Each record has a unique sequence number
 * that is greater than all log records created before it.
 * @type {number}
 * @private
 */
goog.debug.LogRecord.prototype.sequenceNumber_ = 0;


/**
 * Exception associated with the record
 * @type {Object}
 * @private
 */
goog.debug.LogRecord.prototype.exception_ = null;


/**
 * Exception text associated with the record
 * @type {?string}
 * @private
 */
goog.debug.LogRecord.prototype.exceptionText_ = null;


/**
 * @define {boolean} Whether to enable log sequence numbers.
 */
goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);


/**
 * A sequence counter for assigning increasing sequence numbers to LogRecord
 * objects.
 * @type {number}
 * @private
 */
goog.debug.LogRecord.nextSequenceNumber_ = 0;


/**
 * Sets all fields of the log record.
 * @param {goog.debug.Logger.Level} level One of the level identifiers.
 * @param {string} msg The string message.
 * @param {string} loggerName The name of the source logger.
 * @param {number=} opt_time Time this log record was created if other than now.
 *     If 0, we use #goog.now.
 * @param {number=} opt_sequenceNumber Sequence number of this log record. This
 *     should only be passed in when restoring a log record from persistence.
 */
goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
    opt_time, opt_sequenceNumber) {
  if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
    this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
        opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
  }

  this.time_ = opt_time || goog.now();
  this.level_ = level;
  this.msg_ = msg;
  this.loggerName_ = loggerName;
  delete this.exception_;
  delete this.exceptionText_;
};


/**
 * Get the source Logger's name.
 *
 * @return {string} source logger name (may be null).
 */
goog.debug.LogRecord.prototype.getLoggerName = function() {
  return this.loggerName_;
};


/**
 * Get the exception that is part of the log record.
 *
 * @return {Object} the exception.
 */
goog.debug.LogRecord.prototype.getException = function() {
  return this.exception_;
};


/**
 * Set the exception that is part of the log record.
 *
 * @param {Object} exception the exception.
 */
goog.debug.LogRecord.prototype.setException = function(exception) {
  this.exception_ = exception;
};


/**
 * Get the exception text that is part of the log record.
 *
 * @return {?string} Exception text.
 */
goog.debug.LogRecord.prototype.getExceptionText = function() {
  return this.exceptionText_;
};


/**
 * Set the exception text that is part of the log record.
 *
 * @param {string} text The exception text.
 */
goog.debug.LogRecord.prototype.setExceptionText = function(text) {
  this.exceptionText_ = text;
};


/**
 * Get the source Logger's name.
 *
 * @param {string} loggerName source logger name (may be null).
 */
goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
  this.loggerName_ = loggerName;
};


/**
 * Get the logging message level, for example Level.SEVERE.
 * @return {goog.debug.Logger.Level} the logging message level.
 */
goog.debug.LogRecord.prototype.getLevel = function() {
  return this.level_;
};


/**
 * Set the logging message level, for example Level.SEVERE.
 * @param {goog.debug.Logger.Level} level the logging message level.
 */
goog.debug.LogRecord.prototype.setLevel = function(level) {
  this.level_ = level;
};


/**
 * Get the "raw" log message, before localization or formatting.
 *
 * @return {string} the raw message string.
 */
goog.debug.LogRecord.prototype.getMessage = function() {
  return this.msg_;
};


/**
 * Set the "raw" log message, before localization or formatting.
 *
 * @param {string} msg the raw message string.
 */
goog.debug.LogRecord.prototype.setMessage = function(msg) {
  this.msg_ = msg;
};


/**
 * Get event time in milliseconds since 1970.
 *
 * @return {number} event time in millis since 1970.
 */
goog.debug.LogRecord.prototype.getMillis = function() {
  return this.time_;
};


/**
 * Set event time in milliseconds since 1970.
 *
 * @param {number} time event time in millis since 1970.
 */
goog.debug.LogRecord.prototype.setMillis = function(time) {
  this.time_ = time;
};


/**
 * Get the sequence number.
 * <p>
 * Sequence numbers are normally assigned in the LogRecord
 * constructor, which assigns unique sequence numbers to
 * each new LogRecord in increasing order.
 * @return {number} the sequence number.
 */
goog.debug.LogRecord.prototype.getSequenceNumber = function() {
  return this.sequenceNumber_;
};


// Input 20
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A buffer for log records. The purpose of this is to improve
 * logging performance by re-using old objects when the buffer becomes full and
 * to eliminate the need for each app to implement their own log buffer. The
 * disadvantage to doing this is that log handlers cannot maintain references to
 * log records and expect that they are not overwriten at a later point.
 *
 * @author agrieve@google.com (Andrew Grieve)
 */

goog.provide('goog.debug.LogBuffer');

goog.require('goog.asserts');
goog.require('goog.debug.LogRecord');



/**
 * Creates the log buffer.
 * @constructor
 */
goog.debug.LogBuffer = function() {
  goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
      'Cannot use goog.debug.LogBuffer without defining ' +
      'goog.debug.LogBuffer.CAPACITY.');
  this.clear();
};


/**
 * A static method that always returns the same instance of LogBuffer.
 * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
 */
goog.debug.LogBuffer.getInstance = function() {
  if (!goog.debug.LogBuffer.instance_) {
    // This function is written with the return statement after the assignment
    // to avoid the jscompiler StripCode bug described in http://b/2608064.
    // After that bug is fixed this can be refactored.
    goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
  }
  return goog.debug.LogBuffer.instance_;
};


/**
 * @define {number} The number of log records to buffer. 0 means disable
 * buffering.
 */
goog.define('goog.debug.LogBuffer.CAPACITY', 0);


/**
 * The array to store the records.
 * @type {!Array.<!goog.debug.LogRecord|undefined>}
 * @private
 */
goog.debug.LogBuffer.prototype.buffer_;


/**
 * The index of the most recently added record or -1 if there are no records.
 * @type {number}
 * @private
 */
goog.debug.LogBuffer.prototype.curIndex_;


/**
 * Whether the buffer is at capacity.
 * @type {boolean}
 * @private
 */
goog.debug.LogBuffer.prototype.isFull_;


/**
 * Adds a log record to the buffer, possibly overwriting the oldest record.
 * @param {goog.debug.Logger.Level} level One of the level identifiers.
 * @param {string} msg The string message.
 * @param {string} loggerName The name of the source logger.
 * @return {!goog.debug.LogRecord} The log record.
 */
goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
  var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
  this.curIndex_ = curIndex;
  if (this.isFull_) {
    var ret = this.buffer_[curIndex];
    ret.reset(level, msg, loggerName);
    return ret;
  }
  this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
  return this.buffer_[curIndex] =
      new goog.debug.LogRecord(level, msg, loggerName);
};


/**
 * @return {boolean} Whether the log buffer is enabled.
 */
goog.debug.LogBuffer.isBufferingEnabled = function() {
  return goog.debug.LogBuffer.CAPACITY > 0;
};


/**
 * Removes all buffered log records.
 */
goog.debug.LogBuffer.prototype.clear = function() {
  this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
  this.curIndex_ = -1;
  this.isFull_ = false;
};


/**
 * Calls the given function for each buffered log record, starting with the
 * oldest one.
 * @param {function(!goog.debug.LogRecord)} func The function to call.
 */
goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
  var buffer = this.buffer_;
  // Corner case: no records.
  if (!buffer[0]) {
    return;
  }
  var curIndex = this.curIndex_;
  var i = this.isFull_ ? curIndex : -1;
  do {
    i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
    func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
  } while (i != curIndex);
};


// Input 21
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the Logger class. Please minimize dependencies
 * this file has on other closure classes as any dependency it takes won't be
 * able to use the logging infrastructure.
 *
 * @see ../demos/debug.html
 */

goog.provide('goog.debug.LogManager');
goog.provide('goog.debug.Logger');
goog.provide('goog.debug.Logger.Level');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.LogBuffer');
goog.require('goog.debug.LogRecord');



/**
 * The Logger is an object used for logging debug messages. Loggers are
 * normally named, using a hierarchical dot-separated namespace. Logger names
 * can be arbitrary strings, but they should normally be based on the package
 * name or class name of the logged component, such as goog.net.BrowserChannel.
 *
 * The Logger object is loosely based on the java class
 * java.util.logging.Logger. It supports different levels of filtering for
 * different loggers.
 *
 * The logger object should never be instantiated by application code. It
 * should always use the goog.debug.Logger.getLogger function.
 *
 * @constructor
 * @param {string} name The name of the Logger.
 */
goog.debug.Logger = function(name) {
  /**
   * Name of the Logger. Generally a dot-separated namespace
   * @type {string}
   * @private
   */
  this.name_ = name;
};


/**
 * Parent Logger.
 * @type {goog.debug.Logger}
 * @private
 */
goog.debug.Logger.prototype.parent_ = null;


/**
 * Level that this logger only filters above. Null indicates it should
 * inherit from the parent.
 * @type {goog.debug.Logger.Level}
 * @private
 */
goog.debug.Logger.prototype.level_ = null;


/**
 * Map of children loggers. The keys are the leaf names of the children and
 * the values are the child loggers.
 * @type {Object}
 * @private
 */
goog.debug.Logger.prototype.children_ = null;


/**
 * Handlers that are listening to this logger.
 * @type {Array.<Function>}
 * @private
 */
goog.debug.Logger.prototype.handlers_ = null;


/**
 * @define {boolean} Toggles whether loggers other than the root logger can have
 *     log handlers attached to them and whether they can have their log level
 *     set. Logging is a bit faster when this is set to false.
 */
goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);


if (!goog.debug.Logger.ENABLE_HIERARCHY) {
  /**
   * @type {!Array.<Function>}
   * @private
   */
  goog.debug.Logger.rootHandlers_ = [];


  /**
   * @type {goog.debug.Logger.Level}
   * @private
   */
  goog.debug.Logger.rootLevel_;
}



/**
 * The Level class defines a set of standard logging levels that
 * can be used to control logging output.  The logging Level objects
 * are ordered and are specified by ordered integers.  Enabling logging
 * at a given level also enables logging at all higher levels.
 * <p>
 * Clients should normally use the predefined Level constants such
 * as Level.SEVERE.
 * <p>
 * The levels in descending order are:
 * <ul>
 * <li>SEVERE (highest value)
 * <li>WARNING
 * <li>INFO
 * <li>CONFIG
 * <li>FINE
 * <li>FINER
 * <li>FINEST  (lowest value)
 * </ul>
 * In addition there is a level OFF that can be used to turn
 * off logging, and a level ALL that can be used to enable
 * logging of all messages.
 *
 * @param {string} name The name of the level.
 * @param {number} value The numeric value of the level.
 * @constructor
 */
goog.debug.Logger.Level = function(name, value) {
  /**
   * The name of the level
   * @type {string}
   */
  this.name = name;

  /**
   * The numeric value of the level
   * @type {number}
   */
  this.value = value;
};


/**
 * @return {string} String representation of the logger level.
 * @override
 */
goog.debug.Logger.Level.prototype.toString = function() {
  return this.name;
};


/**
 * OFF is a special level that can be used to turn off logging.
 * This level is initialized to <CODE>Infinity</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.OFF =
    new goog.debug.Logger.Level('OFF', Infinity);


/**
 * SHOUT is a message level for extra debugging loudness.
 * This level is initialized to <CODE>1200</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);


/**
 * SEVERE is a message level indicating a serious failure.
 * This level is initialized to <CODE>1000</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);


/**
 * WARNING is a message level indicating a potential problem.
 * This level is initialized to <CODE>900</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);


/**
 * INFO is a message level for informational messages.
 * This level is initialized to <CODE>800</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);


/**
 * CONFIG is a message level for static configuration messages.
 * This level is initialized to <CODE>700</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);


/**
 * FINE is a message level providing tracing information.
 * This level is initialized to <CODE>500</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);


/**
 * FINER indicates a fairly detailed tracing message.
 * This level is initialized to <CODE>400</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);

/**
 * FINEST indicates a highly detailed tracing message.
 * This level is initialized to <CODE>300</CODE>.
 * @type {!goog.debug.Logger.Level}
 */

goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);


/**
 * ALL indicates that all messages should be logged.
 * This level is initialized to <CODE>0</CODE>.
 * @type {!goog.debug.Logger.Level}
 */
goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);


/**
 * The predefined levels.
 * @type {!Array.<!goog.debug.Logger.Level>}
 * @final
 */
goog.debug.Logger.Level.PREDEFINED_LEVELS = [
  goog.debug.Logger.Level.OFF,
  goog.debug.Logger.Level.SHOUT,
  goog.debug.Logger.Level.SEVERE,
  goog.debug.Logger.Level.WARNING,
  goog.debug.Logger.Level.INFO,
  goog.debug.Logger.Level.CONFIG,
  goog.debug.Logger.Level.FINE,
  goog.debug.Logger.Level.FINER,
  goog.debug.Logger.Level.FINEST,
  goog.debug.Logger.Level.ALL];


/**
 * A lookup map used to find the level object based on the name or value of
 * the level object.
 * @type {Object}
 * @private
 */
goog.debug.Logger.Level.predefinedLevelsCache_ = null;


/**
 * Creates the predefined levels cache and populates it.
 * @private
 */
goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
  goog.debug.Logger.Level.predefinedLevelsCache_ = {};
  for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
       i++) {
    goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
    goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
  }
};


/**
 * Gets the predefined level with the given name.
 * @param {string} name The name of the level.
 * @return {goog.debug.Logger.Level} The level, or null if none found.
 */
goog.debug.Logger.Level.getPredefinedLevel = function(name) {
  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
    goog.debug.Logger.Level.createPredefinedLevelsCache_();
  }

  return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
};


/**
 * Gets the highest predefined level <= #value.
 * @param {number} value Level value.
 * @return {goog.debug.Logger.Level} The level, or null if none found.
 */
goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
  if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
    goog.debug.Logger.Level.createPredefinedLevelsCache_();
  }

  if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
    return goog.debug.Logger.Level.predefinedLevelsCache_[value];
  }

  for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
    var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
    if (level.value <= value) {
      return level;
    }
  }
  return null;
};


/**
 * Finds or creates a logger for a named subsystem. If a logger has already been
 * created with the given name it is returned. Otherwise a new logger is
 * created. If a new logger is created its log level will be configured based
 * on the LogManager configuration and it will configured to also send logging
 * output to its parent's handlers. It will be registered in the LogManager
 * global namespace.
 *
 * @param {string} name A name for the logger. This should be a dot-separated
 * name and should normally be based on the package name or class name of the
 * subsystem, such as goog.net.BrowserChannel.
 * @return {!goog.debug.Logger} The named logger.
 */
goog.debug.Logger.getLogger = function(name) {
  return goog.debug.LogManager.getLogger(name);
};


/**
 * Logs a message to profiling tools, if available.
 * {@see http://code.google.com/webtoolkit/speedtracer/logging-api.html}
 * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
 * @param {string} msg The message to log.
 */
goog.debug.Logger.logToProfilers = function(msg) {
  // Using goog.global, as loggers might be used in window-less contexts.
  if (goog.global['console']) {
    if (goog.global['console']['timeStamp']) {
      // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
      goog.global['console']['timeStamp'](msg);
    } else if (goog.global['console']['markTimeline']) {
      // TODO(user): markTimeline is deprecated. Drop this else clause entirely
      // after Chrome M14 hits stable.
      goog.global['console']['markTimeline'](msg);
    }
  }

  if (goog.global['msWriteProfilerMark']) {
    // Logs a message to the Microsoft profiler
    goog.global['msWriteProfilerMark'](msg);
  }
};


/**
 * Gets the name of this logger.
 * @return {string} The name of this logger.
 */
goog.debug.Logger.prototype.getName = function() {
  return this.name_;
};


/**
 * Adds a handler to the logger. This doesn't use the event system because
 * we want to be able to add logging to the event system.
 * @param {Function} handler Handler function to add.
 */
goog.debug.Logger.prototype.addHandler = function(handler) {
  if (goog.debug.LOGGING_ENABLED) {
    if (goog.debug.Logger.ENABLE_HIERARCHY) {
      if (!this.handlers_) {
        this.handlers_ = [];
      }
      this.handlers_.push(handler);
    } else {
      goog.asserts.assert(!this.name_,
          'Cannot call addHandler on a non-root logger when ' +
          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
      goog.debug.Logger.rootHandlers_.push(handler);
    }
  }
};


/**
 * Removes a handler from the logger. This doesn't use the event system because
 * we want to be able to add logging to the event system.
 * @param {Function} handler Handler function to remove.
 * @return {boolean} Whether the handler was removed.
 */
goog.debug.Logger.prototype.removeHandler = function(handler) {
  if (goog.debug.LOGGING_ENABLED) {
    var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
        goog.debug.Logger.rootHandlers_;
    return !!handlers && goog.array.remove(handlers, handler);
  } else {
    return false;
  }
};


/**
 * Returns the parent of this logger.
 * @return {goog.debug.Logger} The parent logger or null if this is the root.
 */
goog.debug.Logger.prototype.getParent = function() {
  return this.parent_;
};


/**
 * Returns the children of this logger as a map of the child name to the logger.
 * @return {!Object} The map where the keys are the child leaf names and the
 *     values are the Logger objects.
 */
goog.debug.Logger.prototype.getChildren = function() {
  if (!this.children_) {
    this.children_ = {};
  }
  return this.children_;
};


/**
 * Set the log level specifying which message levels will be logged by this
 * logger. Message levels lower than this value will be discarded.
 * The level value Level.OFF can be used to turn off logging. If the new level
 * is null, it means that this node should inherit its level from its nearest
 * ancestor with a specific (non-null) level value.
 *
 * @param {goog.debug.Logger.Level} level The new level.
 */
goog.debug.Logger.prototype.setLevel = function(level) {
  if (goog.debug.LOGGING_ENABLED) {
    if (goog.debug.Logger.ENABLE_HIERARCHY) {
      this.level_ = level;
    } else {
      goog.asserts.assert(!this.name_,
          'Cannot call setLevel() on a non-root logger when ' +
          'goog.debug.Logger.ENABLE_HIERARCHY is false.');
      goog.debug.Logger.rootLevel_ = level;
    }
  }
};


/**
 * Gets the log level specifying which message levels will be logged by this
 * logger. Message levels lower than this value will be discarded.
 * The level value Level.OFF can be used to turn off logging. If the level
 * is null, it means that this node should inherit its level from its nearest
 * ancestor with a specific (non-null) level value.
 *
 * @return {goog.debug.Logger.Level} The level.
 */
goog.debug.Logger.prototype.getLevel = function() {
  return goog.debug.LOGGING_ENABLED ?
      this.level_ : goog.debug.Logger.Level.OFF;
};


/**
 * Returns the effective level of the logger based on its ancestors' levels.
 * @return {goog.debug.Logger.Level} The level.
 */
goog.debug.Logger.prototype.getEffectiveLevel = function() {
  if (!goog.debug.LOGGING_ENABLED) {
    return goog.debug.Logger.Level.OFF;
  }

  if (!goog.debug.Logger.ENABLE_HIERARCHY) {
    return goog.debug.Logger.rootLevel_;
  }
  if (this.level_) {
    return this.level_;
  }
  if (this.parent_) {
    return this.parent_.getEffectiveLevel();
  }
  goog.asserts.fail('Root logger has no level set.');
  return null;
};


/**
 * Checks if a message of the given level would actually be logged by this
 * logger. This check is based on the Loggers effective level, which may be
 * inherited from its parent.
 * @param {goog.debug.Logger.Level} level The level to check.
 * @return {boolean} Whether the message would be logged.
 */
goog.debug.Logger.prototype.isLoggable = function(level) {
  return goog.debug.LOGGING_ENABLED &&
      level.value >= this.getEffectiveLevel().value;
};


/**
 * Logs a message. If the logger is currently enabled for the
 * given message level then the given message is forwarded to all the
 * registered output Handler objects.
 * @param {goog.debug.Logger.Level} level One of the level identifiers.
 * @param {string} msg The string message.
 * @param {Error|Object=} opt_exception An exception associated with the
 *     message.
 */
goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
  // java caches the effective level, not sure it's necessary here
  if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
    this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
  }
};


/**
 * Creates a new log record and adds the exception (if present) to it.
 * @param {goog.debug.Logger.Level} level One of the level identifiers.
 * @param {string} msg The string message.
 * @param {Error|Object=} opt_exception An exception associated with the
 *     message.
 * @return {!goog.debug.LogRecord} A log record.
 */
goog.debug.Logger.prototype.getLogRecord = function(level, msg, opt_exception) {
  if (goog.debug.LogBuffer.isBufferingEnabled()) {
    var logRecord =
        goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
  } else {
    logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
  }
  if (opt_exception) {
    logRecord.setException(opt_exception);
    logRecord.setExceptionText(
        goog.debug.exposeException(opt_exception, arguments.callee.caller));
  }
  return logRecord;
};


/**
 * Logs a message at the Logger.Level.SHOUT level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.SEVERE level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.WARNING level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.INFO level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.info = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.CONFIG level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.config = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.FINE level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.FINER level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
  }
};


/**
 * Logs a message at the Logger.Level.FINEST level.
 * If the logger is currently enabled for the given message level then the
 * given message is forwarded to all the registered output Handler objects.
 * @param {string} msg The string message.
 * @param {Error=} opt_exception An exception associated with the message.
 */
goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
  if (goog.debug.LOGGING_ENABLED) {
    this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
  }
};


/**
 * Logs a LogRecord. If the logger is currently enabled for the
 * given message level then the given message is forwarded to all the
 * registered output Handler objects.
 * @param {goog.debug.LogRecord} logRecord A log record to log.
 */
goog.debug.Logger.prototype.logRecord = function(logRecord) {
  if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
    this.doLogRecord_(logRecord);
  }
};


/**
 * Logs a LogRecord.
 * @param {goog.debug.LogRecord} logRecord A log record to log.
 * @private
 */
goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
  goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
  if (goog.debug.Logger.ENABLE_HIERARCHY) {
    var target = this;
    while (target) {
      target.callPublish_(logRecord);
      target = target.getParent();
    }
  } else {
    for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
      handler(logRecord);
    }
  }
};


/**
 * Calls the handlers for publish.
 * @param {goog.debug.LogRecord} logRecord The log record to publish.
 * @private
 */
goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
  if (this.handlers_) {
    for (var i = 0, handler; handler = this.handlers_[i]; i++) {
      handler(logRecord);
    }
  }
};


/**
 * Sets the parent of this logger. This is used for setting up the logger tree.
 * @param {goog.debug.Logger} parent The parent logger.
 * @private
 */
goog.debug.Logger.prototype.setParent_ = function(parent) {
  this.parent_ = parent;
};


/**
 * Adds a child to this logger. This is used for setting up the logger tree.
 * @param {string} name The leaf name of the child.
 * @param {goog.debug.Logger} logger The child logger.
 * @private
 */
goog.debug.Logger.prototype.addChild_ = function(name, logger) {
  this.getChildren()[name] = logger;
};


/**
 * There is a single global LogManager object that is used to maintain a set of
 * shared state about Loggers and log services. This is loosely based on the
 * java class java.util.logging.LogManager.
 */
goog.debug.LogManager = {};


/**
 * Map of logger names to logger objects.
 *
 * @type {!Object}
 * @private
 */
goog.debug.LogManager.loggers_ = {};


/**
 * The root logger which is the root of the logger tree.
 * @type {goog.debug.Logger}
 * @private
 */
goog.debug.LogManager.rootLogger_ = null;


/**
 * Initializes the LogManager if not already initialized.
 */
goog.debug.LogManager.initialize = function() {
  if (!goog.debug.LogManager.rootLogger_) {
    goog.debug.LogManager.rootLogger_ = new goog.debug.Logger('');
    goog.debug.LogManager.loggers_[''] = goog.debug.LogManager.rootLogger_;
    goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
  }
};


/**
 * Returns all the loggers.
 * @return {!Object} Map of logger names to logger objects.
 */
goog.debug.LogManager.getLoggers = function() {
  return goog.debug.LogManager.loggers_;
};


/**
 * Returns the root of the logger tree namespace, the logger with the empty
 * string as its name.
 *
 * @return {!goog.debug.Logger} The root logger.
 */
goog.debug.LogManager.getRoot = function() {
  goog.debug.LogManager.initialize();
  return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
};


/**
 * Finds a named logger.
 *
 * @param {string} name A name for the logger. This should be a dot-separated
 * name and should normally be based on the package name or class name of the
 * subsystem, such as goog.net.BrowserChannel.
 * @return {!goog.debug.Logger} The named logger.
 */
goog.debug.LogManager.getLogger = function(name) {
  goog.debug.LogManager.initialize();
  var ret = goog.debug.LogManager.loggers_[name];
  return ret || goog.debug.LogManager.createLogger_(name);
};


/**
 * Creates a function that can be passed to goog.debug.catchErrors. The function
 * will log all reported errors using the given logger.
 * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
 *     Defaults to the root logger.
 * @return {function(Object)} The created function.
 */
goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
  return function(info) {
    var logger = opt_logger || goog.debug.LogManager.getRoot();
    logger.severe('Error: ' + info.message + ' (' + info.fileName +
                  ' @ Line: ' + info.line + ')');
  };
};


/**
 * Creates the named logger. Will also create the parents of the named logger
 * if they don't yet exist.
 * @param {string} name The name of the logger.
 * @return {!goog.debug.Logger} The named logger.
 * @private
 */
goog.debug.LogManager.createLogger_ = function(name) {
  // find parent logger
  var logger = new goog.debug.Logger(name);
  if (goog.debug.Logger.ENABLE_HIERARCHY) {
    var lastDotIndex = name.lastIndexOf('.');
    var parentName = name.substr(0, lastDotIndex);
    var leafName = name.substr(lastDotIndex + 1);
    var parentLogger = goog.debug.LogManager.getLogger(parentName);

    // tell the parent about the child and the child about the parent
    parentLogger.addChild_(leafName, logger);
    logger.setParent_(parentLogger);
  }

  goog.debug.LogManager.loggers_[name] = logger;
  return logger;
};

// Input 22
/**
 *
 * @fileoverview Wrapper for a IndexedDB key range.
 *
 */


goog.provide('ydn.db.IDBKeyRange');
goog.provide('ydn.db.KeyRange');



/**
 * For those browser that not implemented IDBKeyRange.
 * @param {IDBKey|undefined} lower The value of the lower bound.
 * @param {IDBKey|undefined} upper  The value of the upper bound.
 * @param {boolean=} opt_lowerOpen  If true, the range excludes the lower bound
 * value.
 * @param {boolean=} opt_upperOpen If true, the range excludes the lower bound
 * value.
 * @constructor
 */
ydn.db.KeyRange = function(lower, upper, opt_lowerOpen, opt_upperOpen) {

  // todo: use new @dict type annotation.

  /**
   * @final
   */
  this['lower'] = lower;
  /**
   * @final
   */
  this['upper'] = upper;
  /**
   * @final
   */
  this['lowerOpen'] = !!opt_lowerOpen;
  /**
   * @final
   */
  this['upperOpen'] = !!opt_upperOpen;

  if (goog.DEBUG && goog.isFunction(Object.freeze)) {
    // NOTE: due to performance penalty (in Chrome) of using freeze and
    // hard to debug on different browser we don't want to use freeze
    // this is experimental.
    // http://news.ycombinator.com/item?id=4415981
    Object.freeze(/** @type {!Object} */ (this));
  }
};


/**
 *
 * @type {IDBKey|undefined}
 */
ydn.db.KeyRange.prototype.lower = undefined;


/**
 *
 * @type {IDBKey|undefined}
 */
ydn.db.KeyRange.prototype.upper = undefined;


/**
 *
 * @type {boolean}
 */
ydn.db.KeyRange.prototype.lowerOpen;


/**
 *
 * @type {boolean}
 */
ydn.db.KeyRange.prototype.upperOpen;


/**
 * @override
 * @return {!Object} in JSON format.
 */
ydn.db.KeyRange.prototype.toJSON = function() {
  return ydn.db.KeyRange.toJSON(this);
};


/**
 *
 * @return {IDBKeyRange}
 */
ydn.db.KeyRange.prototype.toIDBKeyRange = function() {
  return ydn.db.KeyRange.parseIDBKeyRange(this);
};


/**
 * Robust efficient cloning.
 * @param {(ydn.db.KeyRange|ydn.db.IDBKeyRange)=} kr key range to be cloned.
 * @return {!ydn.db.KeyRange|undefined} cloned key range.
 */
ydn.db.KeyRange.clone = function(kr) {
  if (goog.isDefAndNotNull(kr)) {
    return new ydn.db.KeyRange(
        /** @type {IDBKey} */ (kr.lower),
        /** @type {IDBKey} */ (kr.upper),
        !!kr.lowerOpen, !!kr.upperOpen);
  } else {
    return undefined;
  }
};


/**
 * Creates a new key range for a single value.
 *
 * @param {IDBKey} value The single value in the range.
 * @return {!ydn.db.KeyRange} The key range.
 * @expose
 */
ydn.db.KeyRange.only = function(value) {
  return new ydn.db.KeyRange(value, value, false, false);
};


/**
 * Creates a key range with upper and lower bounds.
 *
 * @param {IDBKey|undefined} lower The value of the lower bound.
 * @param {IDBKey|undefined} upper The value of the upper bound.
 * @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
 *     value.
 * @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
 *     value.
 * @return {!ydn.db.KeyRange} The key range.
 * @expose
 */
ydn.db.KeyRange.bound = function(lower, upper,
                                 opt_lowerOpen, opt_upperOpen) {
  return new ydn.db.KeyRange(lower, upper, opt_lowerOpen, opt_upperOpen);
};


/**
 * Creates a key range with a upper bound only, starts at the first record.
 *
 * @param {IDBKey} upper The value of the upper bound.
 * @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
 *     value.
 * @return {!ydn.db.KeyRange} The key range.
 * @expose
 */
ydn.db.KeyRange.upperBound = function(upper, opt_upperOpen) {
  return new ydn.db.KeyRange(undefined, upper, undefined, !!opt_upperOpen);
};


/**
 * Creates a key range with a lower bound only, finishes at the last record.
 *
 * @param {IDBKey} lower The value of the lower bound.
 * @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
 *     value.
 * @return {!ydn.db.KeyRange} The key range.
 * @expose
 */
ydn.db.KeyRange.lowerBound = function(lower, opt_lowerOpen) {
  return new ydn.db.KeyRange(lower, undefined, !!opt_lowerOpen, undefined);
};


/**
 * Helper method for creating useful KeyRange.
 * @param {IDBKey} value value.
 * @return {!ydn.db.KeyRange} The key range.
 */
ydn.db.KeyRange.starts = function(value) {
  var value_upper;
  if (goog.isArray(value)) {
    value_upper = goog.array.clone(value);
    // Note on ordering: array > string > data > number
    value_upper.push('\uffff');
  } else if (goog.isString(value)) {
    value_upper = value + '\uffff';
  } else {
    return ydn.db.KeyRange.only(value);
  }

  return ydn.db.KeyRange.bound(value, value_upper, false, true);
};


/**
 *
 * @param {ydn.db.IDBKeyRange|ydn.db.KeyRange|KeyRangeJson} keyRange
 * IDBKeyRange.
 * @return {!Object} IDBKeyRange in JSON format.
 */
ydn.db.KeyRange.toJSON = function(keyRange) {
  keyRange = keyRange || /** @type {KeyRangeJson} */ ({});
  var out = {
    'lower': keyRange['lower'],
    'upper': keyRange['upper'],
    'lowerOpen': keyRange['lowerOpen'],
    'upperOpen': keyRange['upperOpen']
  };
  return out;
};


/**
 * Read four primitive attributes from the input and return newly created
 * keyRange object.
 * @param {(KeyRangeJson|ydn.db.KeyRange|ydn.db.IDBKeyRange)=} key_range
 * keyRange.
 * @return {ydn.db.KeyRange} equivalent IDBKeyRange. Return null if input
 * is null or undefined.
 */
ydn.db.KeyRange.parseKeyRange = function(key_range) {
  if (!goog.isDefAndNotNull(key_range)) {
    return null;
  }
  if (key_range instanceof ydn.db.KeyRange) {
    return key_range;
  }
  if (goog.isObject(key_range)) {
    return new ydn.db.KeyRange(key_range['lower'], key_range['upper'],
        key_range['lowerOpen'], key_range['upperOpen']);
  } else {
    throw new ydn.debug.error.ArgumentException("Invalid key range: " +
        key_range + ' of type ' + typeof key_range);
  }

};


/**
 * Read four primitive attributes from the input and return newly created
 * keyRange object.
 * @param {(KeyRangeJson|ydn.db.KeyRange|ydn.db.IDBKeyRange|Object)=} opt_key_range
 * keyRange.
 * @return {?IDBKeyRange} equivalent IDBKeyRange. Newly created IDBKeyRange.
 * null if input is null or undefined.
 */
ydn.db.KeyRange.parseIDBKeyRange = function(opt_key_range) {
  if (goog.isDefAndNotNull(opt_key_range)) {
    var key_range = opt_key_range;

    if (goog.isDefAndNotNull(key_range['upper']) && goog.isDefAndNotNull(
        key_range['lower'])) {

      return ydn.db.IDBKeyRange.bound(
          key_range.lower, key_range.upper,
          !!key_range['lowerOpen'], !!key_range['upperOpen']);

    } else if (goog.isDefAndNotNull(key_range.upper)) {
      return ydn.db.IDBKeyRange.upperBound(key_range.upper,
          key_range.upperOpen);
    } else if (goog.isDefAndNotNull(key_range.lower)) {
      return ydn.db.IDBKeyRange.lowerBound(key_range.lower,
          key_range.lowerOpen);
    } else {
      return null;
    }
  } else {
    return null;
  }
};


/**
 *
 * @param {Object|undefined} keyRange
 * @return {string} if not valid key range object, return a message reason.
 */
ydn.db.KeyRange.validate = function(keyRange) {
  if (keyRange instanceof ydn.db.KeyRange) {
    return '';
  } else if (goog.isDefAndNotNull(keyRange)) {
    if (goog.isObject(keyRange)) {
      for (var key in keyRange) {
        if (keyRange.hasOwnProperty(key)) {
          if (!goog.array.contains(['lower', 'upper', 'lowerOpen', 'upperOpen'],
              key)) {
            return 'invalid attribute "' + key + '" in key range object';
          }
        }
      }
      return '';
    } else {
      return 'key range must be an object';
    }
  } else {
    return '';
  }
};


/**
 * AND operation on key range
 * @param {!ydn.db.KeyRange} that
 * @return {!ydn.db.KeyRange} return a new key range of this and that key range.
 */
ydn.db.KeyRange.prototype.and = function(that) {
  var lower = this.lower;
  var upper = this.upper;
  var lowerOpen = this.lowerOpen;
  var upperOpen = this.upperOpen;
  if (goog.isDefAndNotNull(that.lower) &&
      (!goog.isDefAndNotNull(this.lower) || that.lower >= this.lower)) {
    lower = that.lower;
    lowerOpen = that.lowerOpen || this.lowerOpen;
  }
  if (goog.isDefAndNotNull(that.upper) &&
    (!goog.isDefAndNotNull(this.upper) || that.upper <= this.upper)) {
    upper = that.upper;
    upperOpen = that.upperOpen || this.upperOpen;
  }

  return ydn.db.KeyRange.bound(lower, upper, lowerOpen, upperOpen);
};


/**
 * For debug display.
 * @param {ydn.db.KeyRange|IDBKeyRange|undefined} kr
 * @return {string} readable form.
 */
ydn.db.KeyRange.toString = function(kr) {
  if (!kr) {
    return '';
  }
  var str = kr.lowerOpen ? '(' : '[';
  if (goog.isDefAndNotNull(kr.lower)) {
    str += kr.lower + ', ';
  }
  if (goog.isDefAndNotNull(kr.upper)) {
    str += kr.upper;
  }
  str += kr.upperOpen ? ')' : ']';
  return str;
};


/**
 *
 * @param {string} quoted_column_name quoted column name.
 * @param {ydn.db.schema.DataType|undefined} type column type.
 * @param {ydn.db.KeyRange|IDBKeyRange} key_range key range.
 * @param {!Array.<string>} wheres where clauses.
 * @param {!Array.<string>} params SQL params to output by appending.
 */
ydn.db.KeyRange.toSql = function(quoted_column_name, type,
                                 key_range, wheres, params) {

  if (!key_range) {
    return;
  }
  if (!key_range.lowerOpen && !key_range.upperOpen &&
      goog.isDefAndNotNull(key_range.lower) &&
      goog.isDefAndNotNull(key_range.upper) &&
      ydn.db.cmp(key_range.lower, key_range.upper) === 0) {

    wheres.push(quoted_column_name + ' = ?');
    params.push(ydn.db.schema.Index.js2sql(key_range.lower, type));
  } else {

    if (goog.isDefAndNotNull(key_range.lower)) {
      var op = key_range.lowerOpen ? ' > ' : ' >= ';
      wheres.push(quoted_column_name + op + '?');
      params.push(ydn.db.schema.Index.js2sql(key_range.lower, type));
    }
    if (goog.isDefAndNotNull(key_range.upper)) {
      var op = key_range.upperOpen ? ' < ' : ' <= ';
      wheres.push(quoted_column_name + op + '?');
      params.push(ydn.db.schema.Index.js2sql(key_range.upper, type));
    }
  }

};


/**
 *
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.KeyRange}
 */
ydn.db.KeyRange.where = function(op, value, opt_op2, opt_value2) {
  var upper, lower, upperOpen, lowerOpen;
  if (op == 'starts' || op == '^') {
    goog.asserts.assert(goog.isString(value) || goog.isArray(value),
        'key value of starts with must be an array or a string');
    goog.asserts.assert(!goog.isDef(opt_op2), 'op2 must not be defined');
    goog.asserts.assert(!goog.isDef(opt_value2), 'value2 must not be defined');
    return ydn.db.KeyRange.starts(/** @type {string|!Array} */ (value));
  } else if (op == '<' || op == '<=') {
    upper = value;
    upperOpen = op == '<';
  } else if (op == '>' || op == '>=') {
    lower = value;
    lowerOpen = op == '>';
  } else if (op == '=' || op == '==') {
    lower = value;
    upper = value;
  } else {
    throw new ydn.debug.error.ArgumentException('invalid op: ' + op);
  }
  if (opt_op2 == '<' || opt_op2 == '<=') {
    upper = opt_value2;
    upperOpen = opt_op2 == '<';
  } else if (opt_op2 == '>' || opt_op2 == '>=') {
    lower = opt_value2;
    lowerOpen = opt_op2 == '>';
  } else if (goog.isDef(opt_op2)) {
    throw new ydn.debug.error.ArgumentException('invalid op2: ' + opt_op2);
  }
  return ydn.db.KeyRange.bound(lower, upper, lowerOpen, upperOpen);
};


/**
 *
 * @type {function(new:IDBKeyRange)} The IDBKeyRange interface of the IndexedDB
 * API represents a continuous interval over some data type that is used for
 * keys.
 */
ydn.db.IDBKeyRange = goog.global.IDBKeyRange ||
    goog.global.webkitIDBKeyRange || ydn.db.KeyRange;


// Input 23
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Define base constants.
 *
 */


goog.provide('ydn.db.base');
goog.provide('ydn.db.base.Transaction');
goog.require('goog.async.Deferred');
goog.require('ydn.async.Deferred');


/**
 * When key column is not defined, You can access the ROWID of an SQLite table
 * using one the special column names ROWID, _ROWID_, or OID.
 *
 * http://www.sqlite.org/autoinc.html
 * @const
 * @type {string}
 */
ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME = '_ROWID_';


/**
 * SQLite store serialized object into this default column. This library
 * always create table with this default column of type BLOB.
 * @const
 * @type {string}
 */
ydn.db.base.DEFAULT_BLOB_COLUMN = '_default_';


/**
 * Install event dispatcher.
 * @define {boolean} true for dispatching.
 */
ydn.db.base.DISPATCH_EVENT = false;


/**
 * Default result limit during retrieving records from the database.
 * @const
 * @type {number}
 */
ydn.db.base.DEFAULT_RESULT_LIMIT = 100;


/**
 * Default connection time interval in ms.
 * @define {number} ms.
 */
ydn.db.base.DEFAULT_CONNECTION_TIMEOUT = 30 * 60 * 1000;


/**
 * @enum {string} storage mechanism type.
 */
ydn.db.base.Mechanisms = {
  IDB: 'indexeddb',
  USER_DATA: 'userdata',
  LOCAL_STORAGE: 'localstorage',
  MEMORY_STORAGE: 'memory',
  SESSION_STORAGE: 'sessionstorage',
  WEBSQL: 'websql'
};


/**
 * Event types the Transaction can dispatch. COMPLETE events are dispatched
 * when the transaction is committed. If a transaction is aborted it dispatches
 * both an ABORT event and an ERROR event with the ABORT_ERR code. Error events
 * are dispatched on any error.
 *
 * @see {@link goog.db.Transaction.EventTypes}
 *
 * @enum {string}
 */
ydn.db.base.TxEventTypes = {
  COMPLETE: 'complete',
  ABORT: 'abort',
  ERROR: 'error'
};


/**
 * The three possible transaction modes in standard TransactionMode.
 * @see http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0615.html
 * @enum {string}
 */
ydn.db.base.StandardTransactionMode = {
  'READ_ONLY': 'readonly',
  'READ_WRITE': 'readwrite',
  'VERSION_CHANGE': 'versionchange'
};


/**
 * Before Chrome 22, IDBTransaction mode are number. New standard change to
 * string. Chrome 22 still follow standard, but weird new constants are
 * taking from the new standard.
 * HACK: The fun fact with current Chrome 22 defines  webkitIDBTransaction as
 * numeric value, but the database engine expect string format and display
 * deprecated warning.
 * For detail discussion see:
 * https://bitbucket.org/ytkyaw/ydn-db/issue/28
 * http://code.google.com/p/chromium/issues/detail?id=155171
 * https://bitbucket.org/ytkyaw/ydn-db/pull-request/8 Old firefox has them too.
 * https://bitbucket.org/ytkyaw/ydn-db/issue/57
 * @const
 * @type {*}
 * @protected
 */
ydn.db.base.IDBTransaction =
    // old Firefox use predefined numeric enum.
    (goog.global.IDBRequest &&
        ('LOADING' in goog.global.IDBRequest)) ?
        goog.global.IDBTransaction :
        // old chrome use predefined enum, it can be string or numeric. ?
        (goog.global.webkitIDBRequest &&
            // old webkit has this const.
            ('LOADING' in goog.global.webkitIDBRequest &&
            // old Chrome define 1 and use;
            // however Android Webkit define 0, but not used
            goog.global.webkitIDBTransaction.READ_WRITE === 1)) ?
            goog.global.webkitIDBTransaction :
            // for all others, assume standard.
            ydn.db.base.StandardTransactionMode;


/**
 * The three possible transaction modes.
 * @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBTransaction
 * @enum {string|number} string in new standard, number in old.
 */
ydn.db.base.TransactionMode = {
  READ_ONLY: ydn.db.base.IDBTransaction.READ_ONLY,
  READ_WRITE: ydn.db.base.IDBTransaction.READ_WRITE,
  VERSION_CHANGE: ydn.db.base.IDBTransaction.VERSION_CHANGE
};


/**
 * @define {boolean} if true, a default key-value text store should be created
 * in the absent of configuration option.
 */
ydn.db.base.ENABLE_DEFAULT_TEXT_STORE = false;


/**
 * @define {boolean} flag to indicate to enable encryption.
 */
ydn.db.base.ENABLE_ENCRYPTION = false;


/**
 * Cursor direction.
 * @link http://www.w3.org/TR/IndexedDB/#dfn-direction
 * @enum {string} Cursor direction.
 */
ydn.db.base.Direction = {
  NEXT: 'next',
  NEXT_UNIQUE: 'nextunique',
  PREV: 'prev',
  PREV_UNIQUE: 'prevunique'
};


/**
 * @const
 * @type {!Array.<ydn.db.base.Direction>} Cursor directions.
 */
ydn.db.base.DIRECTIONS = [
  ydn.db.base.Direction.NEXT,
  ydn.db.base.Direction.NEXT_UNIQUE,
  ydn.db.base.Direction.PREV,
  ydn.db.base.Direction.PREV_UNIQUE
];


/**
 * Convert flag to direction enum.
 * @param {boolean=} opt_reverse true to reverse direction.
 * @param {boolean=} opt_unique true to unique.
 * @return {ydn.db.base.Direction} IndexedDB cursor direction value.
 */
ydn.db.base.getDirection = function(opt_reverse, opt_unique) {
  if (opt_reverse) {
    return opt_unique ? ydn.db.base.Direction.PREV_UNIQUE :
        ydn.db.base.Direction.PREV;
  } else {
    return opt_unique ? ydn.db.base.Direction.NEXT_UNIQUE :
        ydn.db.base.Direction.NEXT;
  }
};


/**
 * @const
 * @type {IDBFactory} IndexedDb.
 */
ydn.db.base.indexedDb = goog.global.indexedDB ||
    goog.global.mozIndexedDB || goog.global.webkitIndexedDB ||
    goog.global.moz_indexedDB ||
    goog.global['msIndexedDB'];


/**
 * @const
 * @type {string} column name prefix for multiEntry index.
 */
ydn.db.base.PREFIX_MULTIENTRY = 'ydn.db.me:';


/**
 * Query method used in sql iterator.
 * @enum {number}
 */
ydn.db.base.QueryMethod = {
  NONE: 0,
  LIST_KEY: 1,
  LIST_PRIMARY_KEY: 2,
  LIST_KEYS: 3,
  LIST_VALUE: 4,
  GET: 5,
  COUNT: 6
};


/**
 * @typedef {(SQLTransaction|IDBTransaction|Object)}
 */
ydn.db.base.Transaction;

// Input 24
/**
 * @fileoverview About this file.
 *
 * User: kyawtun
 * Date: 21/10/12
 */

/**
 * Portion of this code is obtained from Facebook Inc's IndexedDB-polyfill
 * project under Apache License 2.0.
 *
 * See also in https://github.com/mozilla/releases-mozilla-aurora/blob/master/
 * dom/indexedDB/Key.cpp for key encoding algorithm.
 */

goog.provide('ydn.db.utils');
goog.require('ydn.db.base');


/**
 * Grandfathered function to goog.object.getValueByKeys with supporting for
 * dotted key path.
 * Example usage: getValueByKeys(jsonObj, 'foo.entries')
 *
 * @param {!Object} obj An object to get the value from.  Can be array-like.
 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
 *     (as strings, or nubmers, for array-like objects).  Can also be
 *     specified as a single array of keys.
 * @return {IDBKey|undefined} The resulting value.  If, at any point, the value
 * for a key is undefined, returns undefined.
 * @see goog.object.getValueByKeys
 */
ydn.db.utils.getValueByKeys = function(obj, var_args) {
  var isArrayLike, keys;
  if (arguments.length == 2 && goog.isString(arguments[1])) {
    isArrayLike = true;
    keys = arguments[1].split('.');
  } else {
    isArrayLike = goog.isArrayLike(var_args);
    keys = isArrayLike ? var_args : arguments;
  }

  // Start with the 2nd parameter for the variable parameters syntax.
  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
    obj = obj[keys[i]];
    if (!goog.isDef(obj)) {
      break;
    }
  }

  return /** @type {IDBKey} */ (obj);
};


/**
 * Set object of given key path.
 * @param {Object} obj
 * @param {string} key_path doted key path.
 * @param {*} value value to set.
 */
ydn.db.utils.setValueByKeys = function(obj, key_path, value) {
  if (obj) {
    if (key_path.indexOf('.') == -1) {
      obj[key_path] = value;
      return;
    }
    var paths = key_path.split('.');
    var last_key = paths.pop();
    var key;
    while (key = paths.shift()) {
      if (!goog.isObject(obj[key])) {
        goog.asserts.assert(!goog.isDef(obj[key]), 'key "' + key +
            '" already defined');
        obj[key] = {};
      }
      obj = obj[key];
    }
    obj[last_key] = value;
  }
};


/**
 * @const
 * @type {Object}
 */
ydn.db.utils.ARRAY_TERMINATOR = { };


/**
 * @const
 * @type {number}
 */
ydn.db.utils.BYTE_TERMINATOR = 0;


/**
 * @const
 * @type {number}
 */
ydn.db.utils.TYPE_NUMBER = 1;


/**
 * @const
 * @type {number}
 */
ydn.db.utils.TYPE_DATE = 2;


/**
 * @const
 * @type {number}
 */
ydn.db.utils.TYPE_STRING = 3;


/**
 * @const
 * @type {number}
 */
ydn.db.utils.TYPE_ARRAY = 4;


/**
 * @const
 * @type {number}
 */
ydn.db.utils.MAX_TYPE_BYTE_SIZE = 12; // NOTE: Cannot be greater than 255


/**
 *
 * @param {*} key key to encode.
 * @return {string} encoded key as string.
 */
ydn.db.utils.encodeKey = function(key) {
  var stack = [key], writer = new ydn.db.utils.HexStringWriter(), type = 0,
    dataType, obj;
  while ((obj = stack.pop()) !== undefined) {
    if (type % 4 === 0 && type + ydn.db.utils.TYPE_ARRAY >
      ydn.db.utils.MAX_TYPE_BYTE_SIZE) {
      writer.write(type);
      type = 0;
    }
    dataType = typeof obj;
    if (obj instanceof Array) {
      type += ydn.db.utils.TYPE_ARRAY;
      if (obj.length > 0) {
        stack.push(ydn.db.utils.ARRAY_TERMINATOR);
        var i = obj.length;
        while (i--) stack.push(obj[i]);
        continue;
      }
      else {
        writer.write(type);
      }
    }
    else if (dataType === 'number') {
      type += ydn.db.utils.TYPE_NUMBER;
      writer.write(type);
      ydn.db.utils.encodeNumber(writer, obj);
    }
    else if (obj instanceof Date) {
      type += ydn.db.utils.TYPE_DATE;
      writer.write(type);
      ydn.db.utils.encodeNumber(writer, obj.valueOf());
    }
    else if (dataType === 'string') {
      type += ydn.db.utils.TYPE_STRING;
      writer.write(type);
      ydn.db.utils.encodeString(writer, obj);
    }
    else if (obj === ydn.db.utils.ARRAY_TERMINATOR) {
      writer.write(ydn.db.utils.BYTE_TERMINATOR);
    }
    else return ''; // null;
    type = 0;
  }
  return writer.trim().toString();
};


/**
 *
 * @param {string} encodedKey key to decoded.
 * @return {IDBKey} decoded key.
 */
ydn.db.utils.decodeKey = function(encodedKey) {
  var rootArray = []; // one-element root array that contains the result
  var parentArray = rootArray;
  var type, arrayStack = [], depth, tmp;
  var reader = new ydn.db.utils.HexStringReader(encodedKey);
  while (reader.read() != null) {
    if (reader.current === 0) // end of array
    {
      parentArray = arrayStack.pop();
      continue;
    }
    if (reader.current === null) {
      return rootArray[0];
    }
    do
    {
      depth = reader.current / 4 | 0;
      type = reader.current % 4;
      for (var i = 0; i < depth; i++) {
        tmp = [];
        parentArray.push(tmp);
        arrayStack.push(parentArray);
        parentArray = tmp;
      }
      if (type === 0 && reader.current + ydn.db.utils.TYPE_ARRAY >
        ydn.db.utils.MAX_TYPE_BYTE_SIZE) {
        reader.read();
      }
      else break;
    } while (true);

    if (type === ydn.db.utils.TYPE_NUMBER) {
      parentArray.push(ydn.db.utils.decodeNumber(reader));
    }
    else if (type === ydn.db.utils.TYPE_DATE) {
      parentArray.push(new Date(ydn.db.utils.decodeNumber(reader)));
    }
    else if (type === ydn.db.utils.TYPE_STRING) {
      parentArray.push(ydn.db.utils.decodeString(reader)); // add new
    }
    else if (type === 0) // empty array case
    {
      parentArray = arrayStack.pop();
    }
  }
  return rootArray[0];
};

// Utils
/**
 * @const
 * @type {number}
 */
ydn.db.utils.p16 = 0x10000;
/**
 * @const
 * @type {number}
 */
ydn.db.utils.p32 = 0x100000000;
/**
 * @const
 * @type {number}
 */
ydn.db.utils.p48 = 0x1000000000000;
/**
 * @const
 * @type {number}
 */
ydn.db.utils.p52 = 0x10000000000000;
/**
 * @const
 * @type {number}
 */
ydn.db.utils.pNeg1074 = 5e-324;                      // 2^-1074);
/**
 * @const
 * @type {number}
 */
ydn.db.utils.pNeg1022 = 2.2250738585072014e-308;     // 2^-1022

/**
 *
 * @param {number} number
 * @return {Object} IEEE754 number.
 */
ydn.db.utils.ieee754 = function(number) {
  var s = 0, e = 0, m = 0;
  if (number !== 0) {
    if (isFinite(number)) {
      if (number < 0) {
        s = 1;
        number = -number;
      }
      var p = 0;
      if (number >= ydn.db.utils.pNeg1022) {
        var n = number;
        while (n < 1) {
          p--;
          n *= 2;
        }
        while (n >= 2) {
          p++;
          n /= 2;
        }
        e = p + 1023;
      }
      m = e ? Math.floor((number / Math.pow(2, p) - 1) * ydn.db.utils.p52) :
        Math.floor(number / ydn.db.utils.pNeg1074);
    }
    else {
      e = 0x7FF;
      if (isNaN(number)) {
        m = 2251799813685248; // QNan
      }
      else {
        if (number === -Infinity) s = 1;
      }
    }
  }
  return { sign: s, exponent: e, mantissa: m };
};


/**
 * @private
 * @param writer
 * @param number
 */
ydn.db.utils.encodeNumber = function(writer, number) {
  var iee_number = ydn.db.utils.ieee754(number);
  if (iee_number.sign) {
    iee_number.mantissa = ydn.db.utils.p52 - 1 - iee_number.mantissa;
    iee_number.exponent = 0x7FF - iee_number.exponent;
  }
  var word, m = iee_number.mantissa;

  writer.write((iee_number.sign ? 0 : 0x80) | (iee_number.exponent >> 4));
  writer.write((iee_number.exponent & 0xF) << 4 | (0 | m / ydn.db.utils.p48));

  m %= ydn.db.utils.p48;
  word = 0 | m / ydn.db.utils.p32;
  writer.write(word >> 8, word & 0xFF);

  m %= ydn.db.utils.p32;
  word = 0 | m / ydn.db.utils.p16;
  writer.write(word >> 8, word & 0xFF);

  word = m % ydn.db.utils.p16;
  writer.write(word >> 8, word & 0xFF);
};


/**
 * @private
 * @param reader
 * @return {*}
 */
ydn.db.utils.decodeNumber = function(reader) {
  var b = reader.read() | 0;
  var sign = b >> 7 ? false : true;

  var s = sign ? -1 : 1;

  var e = (b & 0x7F) << 4;
  b = reader.read() | 0;
  e += b >> 4;
  if (sign) e = 0x7FF - e;

  var tmp = [sign ? (0xF - (b & 0xF)) : b & 0xF];
  var i = 6;
  while (i--) tmp.push(sign ? (0xFF - (reader.read() | 0)) : reader.read() | 0);

  var m = 0;
  i = 7;
  while (i--) m = m / 256 + tmp[i];
  m /= 16;

  if (m === 0 && e === 0) return 0;
  return (m + 1) * Math.pow(2, e - 1023) * s;
};

/**
 * @const
 * @type {number}
 */
ydn.db.utils.secondLayer = 0x3FFF + 0x7F;

/**
 * @private
 * @param writer
 * @param string
 */
ydn.db.utils.encodeString = function(writer, string) {
  /* 3 layers:
   Chars 0         - 7E            are encoded as 0xxxxxxx with 1 added
   Chars 7F        - (3FFF+7F)     are encoded as 10xxxxxx xxxxxxxx with 7F
   subtracted
   Chars (3FFF+80) - FFFF          are encoded as 11xxxxxx xxxxxxxx xx000000
   */
  for (var i = 0; i < string.length; i++) {
    var code = string.charCodeAt(i);
    if (code <= 0x7E) {
      writer.write(code + 1);
    }
    else if (code <= ydn.db.utils.secondLayer) {
      code -= 0x7F;
      writer.write(0x80 | code >> 8, code & 0xFF);
    }
    else {
      writer.write(0xC0 | code >> 10, code >> 2 | 0xFF, (code | 3) << 6);
    }
  }
  writer.write(ydn.db.utils.BYTE_TERMINATOR);
};

/**
 * @private
 * @param reader
 * @return {string}
 */
ydn.db.utils.decodeString = function(reader) {
  var buffer = [], layer = 0, unicode = 0, count = 0, $byte, tmp;
  while (true) {
    $byte = reader.read();
    if ($byte === 0 || $byte == null) break;

    if (layer === 0) {
      tmp = $byte >> 6;
      if (tmp < 2 && !isNaN($byte)) { // kyaw: add !isNaN($byte)
        buffer.push(String.fromCharCode($byte - 1));
      }
      else // tmp equals 2 or 3
      {
        layer = tmp;
        unicode = $byte << 10;
        count++;
      }
    }
    else if (layer === 2) {
      buffer.push(String.fromCharCode(unicode + $byte + 0x7F));
      layer = unicode = count = 0;
    }
    else // layer === 3
    {
      if (count === 2) {
        unicode += $byte << 2;
        count++;
      }
      else // count === 3
      {
        buffer.push(String.fromCharCode(unicode | $byte >> 6));
        layer = unicode = count = 0;
      }
    }
  }
  return buffer.join('');
};

/**
 * @private
 * @param string
 * @constructor
 */
ydn.db.utils.HexStringReader = function(string) {
  this.current = null;
  this.string = string;
  this.lastIndex = this.string.length - 1;
  this.index = -1;
};

/**
 * @type {number?}
 */
ydn.db.utils.HexStringReader.prototype.current;

/**
 * @type {number}
 * @private
 */
ydn.db.utils.HexStringReader.prototype.index;

/**
 * @type {number}
 * @private
 */
ydn.db.utils.HexStringReader.prototype.lastIndex;

/**
 * @type {string}
 * @private
 */
ydn.db.utils.HexStringReader.prototype.string;


/**
 *
 * @return {number?}
 */
ydn.db.utils.HexStringReader.prototype.read = function() {

  return this.current = this.index < this.lastIndex ? parseInt(this.string[++this.index] +
    this.string[++this.index], 16) : null;
};

/**
 * @private
 * @constructor
 */
ydn.db.utils.HexStringWriter = function() {

  this.buffer = [];
  this.c = undefined;

};


/**
 * @type {string|undefined}
 * @private
 */
ydn.db.utils.HexStringWriter.prototype.c;

/**
 * @type {Array}
 * @private
 */
ydn.db.utils.HexStringWriter.prototype.buffer;

/**
 *
 * @param $byte
 */
ydn.db.utils.HexStringWriter.prototype.write = function($byte) {
  for (var i = 0; i < arguments.length; i++) {
    this.c = arguments[i].toString(16);
    this.buffer.push(this.c.length === 2 ? this.c : this.c = '0' + this.c);
  }
};


/**
 *
 * @return {ydn.db.utils.HexStringWriter}
 */
ydn.db.utils.HexStringWriter.prototype.trim = function() {
  var length = this.buffer.length;
  while (this.buffer[--length] === '00') {}
  this.buffer.length = ++length;
  return this;
};


/**
 * @return {string}
 */
ydn.db.utils.HexStringWriter.prototype.toString = function() {
  return this.buffer.length ? this.buffer.join('') : '';
};


/**
 *
 * @param {*} first
 * @param {*} second
 * @return {number} returns 1 if the first key is
 * greater than the second, -1 if the first is less than the second, and 0 if
 * the first is equal to the second.
 */
ydn.db.utils.cmp = function (first, second) {
  var key1 = ydn.db.utils.encodeKey(first);
  var key2 = ydn.db.utils.encodeKey(second);
  return key1 > key2 ? 1 : (key1 == key2 ? 0 : -1);
};

// Input 25
/**
 * @fileoverview WHERE clause as keyRange object.
 */


goog.provide('ydn.db.Where');
goog.require('goog.string');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.utils');
goog.require('ydn.debug.error.ArgumentException');



/**
 * For those browser that not implemented IDBKeyRange.
 * @param {string} field index field name to query from.
 * @param {string|KeyRangeJson|ydn.db.KeyRange} op where operator.
 * @param {IDBKey=} opt_value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @constructor
 */
ydn.db.Where = function(field, op, opt_value, opt_op2, opt_value2) {
  /**
   * @final
   * @private
   */
  this.key_range_ = op instanceof ydn.db.KeyRange ?
      op : goog.isString(op) && goog.isDef(opt_value) ?
      ydn.db.KeyRange.where(op, opt_value, opt_op2, opt_value2) :
      ydn.db.KeyRange.parseKeyRange(/** @type {KeyRangeJson} */ (op));
  /**
   * @final
   */
  this.field = field;
};


/**
 *
 * @type {string}
 * @protected
 */
ydn.db.Where.prototype.field = '';


/**
 *
 * @type {ydn.db.KeyRange}
 * @private
 */
ydn.db.Where.prototype.key_range_;


/**
 *
 * @return {string}
 */
ydn.db.Where.prototype.getField = function() {
  return this.field;
};


/**
 *
 * @return {ydn.db.KeyRange}
 */
ydn.db.Where.prototype.getKeyRange = function() {
  return this.key_range_;
};


/**
 * Try to resolve keyRange with starts with keyRange.
 * @param {ydn.db.KeyRange|ydn.db.IDBKeyRange=} keyRange key range to check.
 * @return {boolean} true if given key range can be resolved to starts with
 * keyRange.
 */
ydn.db.Where.resolvedStartsWith = function(keyRange) {
  if (!goog.isDefAndNotNull(keyRange) ||
      !goog.isDefAndNotNull(keyRange.lower) ||
      !goog.isDefAndNotNull(keyRange.upper)) {
    return false;
  }
  if (goog.isArray(keyRange.lower) && goog.isArray(keyRange.upper)) {
    return (keyRange.lower.length == keyRange.upper.length - 1) &&
        keyRange.upper[keyRange.upper.length - 1] == '\uffff' &&
        keyRange.lower.every(function(x, i) {return x == keyRange.upper[i]});
  } else {
    return !keyRange.lowerOpen && !keyRange.upperOpen &&
        keyRange.lower.length == keyRange.upper.length + 1 &&
        keyRange.upper[keyRange.lower.length - 1] == '\uffff';
  }

};


/**
 * Combine another where clause.
 * @param {!ydn.db.Where} that
 * @return {ydn.db.Where} return null if fail.
 */
ydn.db.Where.prototype.and = function(that) {
  if (this.field != that.field) {
    return null;
  }

  var key_range = goog.isDefAndNotNull(this.key_range_) &&
      goog.isDefAndNotNull(that.key_range_) ?
      this.key_range_.and(that.key_range_) : this.key_range_ || that.key_range_;

  return new ydn.db.Where(this.field, key_range);
};




// Input 26
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the disposable interface.  A disposable object
 * has a dispose method to to clean up references and resources.
 * @author nnaze@google.com (Nathan Naze)
 */


goog.provide('goog.disposable.IDisposable');



/**
 * Interface for a disposable object.  If a instance requires cleanup
 * (references COM objects, DOM notes, or other disposable objects), it should
 * implement this interface (it may subclass goog.Disposable).
 * @interface
 */
goog.disposable.IDisposable = function() {};


/**
 * Disposes of the object and its resources.
 * @return {void} Nothing.
 */
goog.disposable.IDisposable.prototype.dispose;


/**
 * @return {boolean} Whether the object has been disposed of.
 */
goog.disposable.IDisposable.prototype.isDisposed;

// Input 27
/**
 * @fileoverview cursor interface.
 */


goog.provide('ydn.db.core.req.ICursor');
goog.require('goog.disposable.IDisposable');



/**
 * @interface
 * @extends {goog.disposable.IDisposable}
 */
ydn.db.core.req.ICursor = function() {};


/**
 *
 * @param {!Error} error
 */
ydn.db.core.req.ICursor.prototype.onError = goog.abstractMethod;


/**
 * onSuccess handler is called before onNext callback. The purpose of
 * onSuccess handler is apply filter. If filter condition are not meet,
 * onSuccess return next advancement value skipping onNext callback.
 *
 * @param {IDBKey=} primary_key
 * @param {IDBKey=} key
 * @param {*=} value
 * @return {*}
 */
ydn.db.core.req.ICursor.prototype.onSuccess = goog.abstractMethod;


/**
 * Make cursor opening request.
 *
 * This will seek to given initial position if given. If only ini_key (primary
 * key) is given, this will rewind, if not found.
 *
 * @param {IDBKey=} opt_ini_key effective key to resume position.
 * @param {IDBKey=} opt_ini_primary_key primary key to resume position.
 */
ydn.db.core.req.ICursor.prototype.openCursor =
    function(opt_ini_key, opt_ini_primary_key) {};


/**
 * Move cursor position to the primary key while remaining on same index key.
 * @param {IDBKey} primary_key primary key position to continue.
 */
ydn.db.core.req.ICursor.prototype.continuePrimaryKey =
    function(primary_key) {};


/**
 * Move cursor position to the effective key.
 * @param {IDBKey=} opt_effective_key effective key position to continue.
 */
ydn.db.core.req.ICursor.prototype.continueEffectiveKey =
    function(opt_effective_key) {};


/**
 * Move cursor position to the effective key.
 * @param {number} number_of_step
 */
ydn.db.core.req.ICursor.prototype.advance = function(number_of_step) {};


/**
 * Restart the cursor. If previous cursor position is given,
 * the position is skip.
 * @param {IDBKey=} effective_key previous position.
 * @param {IDBKey=} primary_key
 */
ydn.db.core.req.ICursor.prototype.restart =
    function(effective_key, primary_key) {};


/**
 * @return {boolean}
 */
ydn.db.core.req.ICursor.prototype.hasCursor = function() {};


/**
 * @param {!Object} obj record value.
 * @return {!goog.async.Deferred} value.
 */
ydn.db.core.req.ICursor.prototype.update = function(obj) {};


/**
 * Clear record
 * @return {!goog.async.Deferred} value.
 */
ydn.db.core.req.ICursor.prototype.clear = function() {};


/**
 *
 * @return {boolean} return true if this is an index cursor.
 */
ydn.db.core.req.ICursor.prototype.isIndexCursor = function() {};


/**
 *
 * @return {boolean} return true if this is an index cursor.
 * @deprecated use isIndexCursor instead.
 */
ydn.db.core.req.ICursor.prototype.isPrimaryCursor = function() {};


/**
 *
 * @return {boolean} return true if this is an value cursor.
 */
ydn.db.core.req.ICursor.prototype.isValueCursor = function() {};

// Input 28
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Implements the disposable interface. The dispose method is used
 * to clean up references and resources.
 * @author arv@google.com (Erik Arvidsson)
 */


goog.provide('goog.Disposable');
goog.provide('goog.dispose');

goog.require('goog.disposable.IDisposable');



/**
 * Class that provides the basic implementation for disposable objects. If your
 * class holds one or more references to COM objects, DOM nodes, or other
 * disposable objects, it should extend this class or implement the disposable
 * interface (defined in goog.disposable.IDisposable).
 * @constructor
 * @implements {goog.disposable.IDisposable}
 */
goog.Disposable = function() {
  if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
    this.creationStack = new Error().stack;
    goog.Disposable.instances_[goog.getUid(this)] = this;
  }
};


/**
 * @enum {number} Different monitoring modes for Disposable.
 */
goog.Disposable.MonitoringMode = {
  /**
   * No monitoring.
   */
  OFF: 0,
  /**
   * Creating and disposing the goog.Disposable instances is monitored. All
   * disposable objects need to call the {@code goog.Disposable} base
   * constructor. The PERMANENT mode must bet switched on before creating any
   * goog.Disposable instances.
   */
  PERMANENT: 1,
  /**
   * INTERACTIVE mode can be switched on and off on the fly without producing
   * errors. It also doesn't warn if the disposable objects don't call the
   * {@code goog.Disposable} base constructor.
   */
  INTERACTIVE: 2
};


/**
 * @define {number} The monitoring mode of the goog.Disposable
 *     instances. Default is OFF. Switching on the monitoring is only
 *     recommended for debugging because it has a significant impact on
 *     performance and memory usage. If switched off, the monitoring code
 *     compiles down to 0 bytes.
 */
goog.define('goog.Disposable.MONITORING_MODE', 0);


/**
 * Maps the unique ID of every undisposed {@code goog.Disposable} object to
 * the object itself.
 * @type {!Object.<number, !goog.Disposable>}
 * @private
 */
goog.Disposable.instances_ = {};


/**
 * @return {!Array.<!goog.Disposable>} All {@code goog.Disposable} objects that
 *     haven't been disposed of.
 */
goog.Disposable.getUndisposedObjects = function() {
  var ret = [];
  for (var id in goog.Disposable.instances_) {
    if (goog.Disposable.instances_.hasOwnProperty(id)) {
      ret.push(goog.Disposable.instances_[Number(id)]);
    }
  }
  return ret;
};


/**
 * Clears the registry of undisposed objects but doesn't dispose of them.
 */
goog.Disposable.clearUndisposedObjects = function() {
  goog.Disposable.instances_ = {};
};


/**
 * Whether the object has been disposed of.
 * @type {boolean}
 * @private
 */
goog.Disposable.prototype.disposed_ = false;


/**
 * Callbacks to invoke when this object is disposed.
 * @type {Array.<!Function>}
 * @private
 */
goog.Disposable.prototype.onDisposeCallbacks_;


/**
 * If monitoring the goog.Disposable instances is enabled, stores the creation
 * stack trace of the Disposable instance.
 * @type {string}
 */
goog.Disposable.prototype.creationStack;


/**
 * @return {boolean} Whether the object has been disposed of.
 * @override
 */
goog.Disposable.prototype.isDisposed = function() {
  return this.disposed_;
};


/**
 * @return {boolean} Whether the object has been disposed of.
 * @deprecated Use {@link #isDisposed} instead.
 */
goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;


/**
 * Disposes of the object. If the object hasn't already been disposed of, calls
 * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
 * override {@link #disposeInternal} in order to delete references to COM
 * objects, DOM nodes, and other disposable objects. Reentrant.
 *
 * @return {void} Nothing.
 * @override
 */
goog.Disposable.prototype.dispose = function() {
  if (!this.disposed_) {
    // Set disposed_ to true first, in case during the chain of disposal this
    // gets disposed recursively.
    this.disposed_ = true;
    this.disposeInternal();
    if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
      var uid = goog.getUid(this);
      if (goog.Disposable.MONITORING_MODE ==
          goog.Disposable.MonitoringMode.PERMANENT &&
          !goog.Disposable.instances_.hasOwnProperty(uid)) {
        throw Error(this + ' did not call the goog.Disposable base ' +
            'constructor or was disposed of after a clearUndisposedObjects ' +
            'call');
      }
      delete goog.Disposable.instances_[uid];
    }
  }
};


/**
 * Associates a disposable object with this object so that they will be disposed
 * together.
 * @param {goog.disposable.IDisposable} disposable that will be disposed when
 *     this object is disposed.
 */
goog.Disposable.prototype.registerDisposable = function(disposable) {
  this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
};


/**
 * Invokes a callback function when this object is disposed. Callbacks are
 * invoked in the order in which they were added.
 * @param {function(this:T):?} callback The callback function.
 * @param {T=} opt_scope An optional scope to call the callback in.
 * @template T
 */
goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
  if (!this.onDisposeCallbacks_) {
    this.onDisposeCallbacks_ = [];
  }
  this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope));
};


/**
 * Deletes or nulls out any references to COM objects, DOM nodes, or other
 * disposable objects. Classes that extend {@code goog.Disposable} should
 * override this method.
 * Not reentrant. To avoid calling it twice, it must only be called from the
 * subclass' {@code disposeInternal} method. Everywhere else the public
 * {@code dispose} method must be used.
 * For example:
 * <pre>
 *   mypackage.MyClass = function() {
 *     goog.base(this);
 *     // Constructor logic specific to MyClass.
 *     ...
 *   };
 *   goog.inherits(mypackage.MyClass, goog.Disposable);
 *
 *   mypackage.MyClass.prototype.disposeInternal = function() {
 *     // Dispose logic specific to MyClass.
 *     ...
 *     // Call superclass's disposeInternal at the end of the subclass's, like
 *     // in C++, to avoid hard-to-catch issues.
 *     goog.base(this, 'disposeInternal');
 *   };
 * </pre>
 * @protected
 */
goog.Disposable.prototype.disposeInternal = function() {
  if (this.onDisposeCallbacks_) {
    while (this.onDisposeCallbacks_.length) {
      this.onDisposeCallbacks_.shift()();
    }
  }
};


/**
 * Returns True if we can verify the object is disposed.
 * Calls {@code isDisposed} on the argument if it supports it.  If obj
 * is not an object with an isDisposed() method, return false.
 * @param {*} obj The object to investigate.
 * @return {boolean} True if we can verify the object is disposed.
 */
goog.Disposable.isDisposed = function(obj) {
  if (obj && typeof obj.isDisposed == 'function') {
    return obj.isDisposed();
  }
  return false;
};


/**
 * Calls {@code dispose} on the argument if it supports it. If obj is not an
 *     object with a dispose() method, this is a no-op.
 * @param {*} obj The object to dispose of.
 */
goog.dispose = function(obj) {
  if (obj && typeof obj.dispose == 'function') {
    obj.dispose();
  }
};


/**
 * Calls {@code dispose} on each member of the list that supports it. (If the
 * member is an ArrayLike, then {@code goog.disposeAll()} will be called
 * recursively on each of its members.) If the member is not an object with a
 * {@code dispose()} method, then it is ignored.
 * @param {...*} var_args The list.
 */
goog.disposeAll = function(var_args) {
  for (var i = 0, len = arguments.length; i < len; ++i) {
    var disposable = arguments[i];
    if (goog.isArrayLike(disposable)) {
      goog.disposeAll.apply(null, disposable);
    } else {
      goog.dispose(disposable);
    }
  }
};

// Input 29
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Cursor.
 */


goog.provide('ydn.db.core.req.AbstractCursor');
goog.require('goog.Disposable');
goog.require('ydn.debug.error.InternalError');



/**
 * Open an index. This will resume depending on the cursor state.
 * @param {ydn.db.base.Transaction} tx tx.
 * @param {string} tx_no tx no.
 * @param {ydn.db.schema.Store} store_schema schema.
 * @param {ydn.db.base.QueryMethod=} opt_mth query method, default to
 * values.
 * @constructor
 * @extends {goog.Disposable}
 * @implements {ydn.db.core.req.ICursor}
 * @struct
 * @suppress {checkStructDictInheritance} suppress closure-library code.
 */
ydn.db.core.req.AbstractCursor = function(tx, tx_no, store_schema, opt_mth) {
  goog.base(this);
  /**
   * @final
   * @protected
   * @type {ydn.db.schema.Store}
   */
  this.store_schema = store_schema;
  /**
   * @final
   * @protected
   */
  this.store_name = store_schema.getName();
  /**
   * @protected
   */
  this.index_name = undefined;
  /**
   * @protected
   */
  this.is_index = false;

  /**
   * @protected
   */
  this.key_range = null;

  this.tx = tx;

  this.tx_no = tx_no;

  this.count_ = 0;
  /**
   * @type {boolean}
   * @private
   */
  this.done_ = false;
  /**
   * @type {boolean}
   * @private
   */
  this.exited_ = false;

  /**
   * @protected
   */
  this.query_method = opt_mth || ydn.db.base.QueryMethod.LIST_VALUE;
  /**
   * @type {IDBKey|undefined}
   * @private
   */
  this.key_ = undefined;
  /**
   * @type {IDBKey|undefined}
   * @private
   */
  this.primary_key_ = undefined;
  /**
   * @type {*}
   * @private
   */
  this.value_ = undefined;

  /**
   * @type {boolean|undefined}
   * @protected
   */
  this.reverse;

  /**
   * @type {boolean|undefined}
   * @protected
   */
  this.unique;

  /**
   * This method is overridden by cursor consumer.
   * @param {IDBKey?=} opt_key effective key.
   */
  this.onNext = function(opt_key) {
    throw new ydn.debug.error.InternalError();
  };

  /**
   * This method is overridden by cursor consumer.
   * @param {Error|SQLError} e error.
   */
  this.onFail = function(e) {
    throw new ydn.debug.error.InternalError();
  };
  /**
   * Invoke when cursor terminate.
   * This method is overridden by iterator.
   * @param {boolean} is_existed existed cursor.
   * @param {IDBKey|undefined} key effective key.
   * @param {IDBKey|undefined} primary_key primary key.
   */
  this.onTerminated = function(is_existed, key, primary_key) {
  };

};
goog.inherits(ydn.db.core.req.AbstractCursor, goog.Disposable);


/**
 * Open an index. This will resume depending on the cursor state.
 * @param {string} store_name the store name to open.
 * @param {!Array.<string>|string|undefined} index_name index name.
 * @param {IDBKeyRange} key_range key range.
 * @param {ydn.db.base.Direction} direction cursor direction.
 * @param {boolean} is_key_cursor mode.
 */
ydn.db.core.req.AbstractCursor.prototype.init = function(store_name,
    index_name, key_range, direction, is_key_cursor) {
  goog.asserts.assert(this.store_name == store_name, 'expect store name of ' +
      this.store_name + ' but ' + store_name + ' found.');

  if (goog.isDef(index_name)) {
    this.index_name = this.store_schema.getIndexName(index_name);
    goog.asserts.assertString(this.index_name, 'index "' +
        index_name + '" not found in store "' + store_name + '"');
  }
  this.is_index = goog.isString(this.index_name);
  this.key_range = key_range || null;
  this.count_ = 0;
  this.done_ = false;
  this.exited_ = false;
  this.reverse = direction == ydn.db.base.Direction.PREV ||
      direction == ydn.db.base.Direction.PREV_UNIQUE;
  this.unique = direction == ydn.db.base.Direction.NEXT_UNIQUE ||
      direction == ydn.db.base.Direction.PREV_UNIQUE;
  this.dir = direction;
  this.is_key_cursor_ = is_key_cursor;
  this.key_ = undefined;
  this.primary_key_ = undefined;
  this.value_ = undefined;
};


/**
 * @protected
 * @type {string|undefined}
 */
ydn.db.core.req.AbstractCursor.prototype.index_name;


/**
 * @protected
 * @type {boolean}
 */
ydn.db.core.req.AbstractCursor.prototype.is_index;


/**
 * @protected
 * @type {string}
 */
ydn.db.core.req.AbstractCursor.prototype.store_name;


/**
 * @protected
 * @type {string}
 */
ydn.db.core.req.AbstractCursor.prototype.dir = '';


/**
 * @protected
 * @type {IDBKeyRange}
 */
ydn.db.core.req.AbstractCursor.prototype.key_range = null;


/**
 * @protected
 * @type {boolean}
 */
ydn.db.core.req.AbstractCursor.prototype.unique = false;


/**
 * @protected
 * @type {boolean}
 */
ydn.db.core.req.AbstractCursor.prototype.reverse = false;


/**
 * @private
 * @type {boolean}
 */
ydn.db.core.req.AbstractCursor.prototype.is_key_cursor_ = true;


/**
 * @protected
 * @type {ydn.db.base.QueryMethod}
 */
ydn.db.core.req.AbstractCursor.prototype.query_method;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.AbstractCursor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.AbstractCursor');


/**
 *
 * @return {boolean} true if transaction is active.
 */
ydn.db.core.req.AbstractCursor.prototype.isActive = function() {
  return !!this.tx;
};


/**
 *
 * @return {boolean} return true if this is an index cursor.
 */
ydn.db.core.req.AbstractCursor.prototype.isIndexCursor = function() {
  return this.is_index;
};


/**
 *
 * @return {boolean} return true if this is an index cursor.
 */
ydn.db.core.req.AbstractCursor.prototype.isPrimaryCursor = function() {
  return !this.is_index;
};


/**
 *
 * @return {boolean} return true if this is an value cursor.
 */
ydn.db.core.req.AbstractCursor.prototype.isValueCursor = function() {
  return !this.is_key_cursor_;
};


/**
 * Callback on request error.
 * @param {Error|SQLError} e error object.
 */
ydn.db.core.req.AbstractCursor.prototype.onError = function(e) {
  this.onFail(e);
  this.finalize_();
  this.done_ = true;
};


/**
 * Move cursor to a given position by primary key.
 *
 * This will iterate the cursor records until the primary key is found without
 * changing index key. If index has change during iteration, this will invoke
 * onNext callback with resulting value. If given primary key is in wrong
 * direction, this will rewind and seek.
 *
 * Return value of:
 *   undefined : will invoke onNext
 *   null      : don't do anything
 *   *         : seek to given primary key value, not invoke onNext.
 *   true      : continue next cursor position, not invoke onNext
 *   false     : restart the cursor, not invoke onNext.
 *
 * @param {IDBKey=} opt_key
 * @param {IDBKey=} opt_primary_key
 * @param {*=} opt_value
 */
ydn.db.core.req.AbstractCursor.prototype.onSuccess = function(
    opt_key, opt_primary_key, opt_value) {
  // console.log(this.count_, opt_key, opt_primary_key, opt_value);
  if (!goog.isDefAndNotNull(opt_key)) {
    this.logger.finer(this + ' finished.');
    this.done_ = true;
  }
  this.key_ = opt_key;
  this.primary_key_ = opt_primary_key;
  this.value_ = opt_value;

  this.count_++;
  if (this.done_) {
    this.logger.finest(this + ' DONE.');
    this.onNext();
    this.finalize_();
  } else {
    var key_str = this.is_index ?
        this.key_ + ', ' + this.primary_key_ : this.key_;
    this.logger.finest(this + ' new cursor position {' + key_str + '}');
    this.onNext(this.key_);
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.AbstractCursor.prototype.disposeInternal = function() {
  this.tx = null;
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.core.req.AbstractCursor.prototype.toString = function() {
    var index = goog.isDef(this.index_name) ? ':' + this.index_name : '';
    var active = this.tx ? '' : '~';
    return 'Cursor:' + this.store_name +
        index + '[' + active + this.tx_no + ']';
  };
}


/**
 * Copy keys from cursors before browser GC them, as cursor lift-time expires.
 * http://www.w3.org/TR/IndexedDB/#dfn-transaction-lifetime
 * Keys are used to resume cursors position.
 * @private
 */
ydn.db.core.req.AbstractCursor.prototype.finalize_ = function() {
  // IndexedDB will GC array keys, so we clone it.
  if (goog.isDefAndNotNull(this.primary_key_)) {
    this.primary_key_ = ydn.db.Key.clone(this.primary_key_);
  } else {
    this.primary_key_ = undefined;
  }
  if (goog.isDefAndNotNull(this.key_)) {
    this.key_ = ydn.db.Key.clone(this.key_);
  } else {
    this.key_ = undefined;
  }
  this.onTerminated(this.exited_, this.key_, this.primary_key_);
};


/**
 * Make cursor opening request.
 *
 * This will seek to given initial position if given. If only ini_key (primary
 * key) is given, this will rewind, if not found.
 *
 * @param {IDBKey=} opt_ini_key effective key to resume position.
 * @param {IDBKey=} opt_ini_primary_key primary key to resume position.
 */
ydn.db.core.req.AbstractCursor.prototype.openCursor = goog.abstractMethod;


/**
 * @param {ydn.db.base.Transaction} tx tx.
 * @param {string} tx_no tx no.
 * @param {IDBKey=} opt_ini_key effective key to resume position.
 * @param {IDBKey=} opt_ini_primary_key primary key to resume position.
 */
ydn.db.core.req.AbstractCursor.prototype.open = function(tx, tx_no,
    opt_ini_key, opt_ini_primary_key) {
  this.tx = tx;
  this.tx_no = tx_no;
  this.exited_ = false;
  this.done_ = false;
  this.key_ = opt_ini_key;
  this.primary_key_ = opt_ini_primary_key;
  this.openCursor(this.key_, this.primary_key_);
};


/**
 * Resume cursor.
 * @param {ydn.db.base.Transaction} tx tx.
 * @param {string} tx_no tx no.
 * @final
 */
ydn.db.core.req.AbstractCursor.prototype.resume = function(tx, tx_no) {
  if (this.done_) {
    this.open(tx, tx_no);
  } else {
    this.open(tx, tx_no, this.key_, this.primary_key_);
  }
};


/**
 * Exit cursor
 */
ydn.db.core.req.AbstractCursor.prototype.exit = function() {
  this.exited_ = true;
  this.logger.finest(this + ': exit');
  this.finalize_();
};


/**
 * @return {number} Number of steps iterated.
 */
ydn.db.core.req.AbstractCursor.prototype.getCount = function() {
  return this.count_;
};


/**
 * @return {IDBKey|undefined} effective key of cursor.
 */
ydn.db.core.req.AbstractCursor.prototype.getKey = function() {
  return this.key_;
};


/**
 * @return {IDBKey|undefined} primary key of cursor.
 */
ydn.db.core.req.AbstractCursor.prototype.getPrimaryKey = function() {
  return this.isIndexCursor() ?
      this.primary_key_ : this.key_;
};


/**
 * @param {number=} opt_idx cursor index.
 * @return {*} value.
 */
ydn.db.core.req.AbstractCursor.prototype.getValue = function(opt_idx) {
  return this.isValueCursor() ?
      this.value_ : this.getPrimaryKey();
};


/**
 *
 * @return {boolean} true if cursor gone.
 */
ydn.db.core.req.AbstractCursor.prototype.hasDone = function() {
  return this.done_;
};


/**
 *
 * @return {boolean} true if iteration is existed.
 */
ydn.db.core.req.AbstractCursor.prototype.isExited = function() {
  return this.exited_;
};


/**
 * Move cursor position to the primary key while remaining on same index key.
 * @param {IDBKey} primary_key primary key position to continue.
 */
ydn.db.core.req.AbstractCursor.prototype.continuePrimaryKey =
    function(primary_key) {};


/**
 * Move cursor position to the effective key.
 * @param {IDBKey=} opt_effective_key effective key position to continue.
 */
ydn.db.core.req.AbstractCursor.prototype.continueEffectiveKey =
    function(opt_effective_key) {};


/**
 * Move cursor position to the effective key.
 * @param {number} number_of_step
 */
ydn.db.core.req.AbstractCursor.prototype.advance = goog.abstractMethod;


/**
 * @return {boolean}
 */
ydn.db.core.req.AbstractCursor.prototype.hasCursor = goog.abstractMethod;


/**
 * @param {!Object} obj record value.
 * @return {!goog.async.Deferred} value.
 */
ydn.db.core.req.AbstractCursor.prototype.update = goog.abstractMethod;


/**
 * Clear record
 * @return {!goog.async.Deferred} value.
 */
ydn.db.core.req.AbstractCursor.prototype.clear = goog.abstractMethod;


/**
 * Restart the cursor. If previous cursor position is given,
 * the position is skip.
 * @param {IDBKey=} effective_key previous position.
 * @param {IDBKey=} primary_key
 * @final
 */
ydn.db.core.req.AbstractCursor.prototype.restart = function(
    effective_key, primary_key) {
  this.logger.finest(this + ' restarting');
  this.done_ = false;
  this.exited_ = false;
  this.openCursor(primary_key, effective_key);
};


// Input 30
// Copyright 2013 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Abstract iterator.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.core.AbstractIterator');
goog.require('ydn.db.core.req.AbstractCursor');


/**
 * Abstract iterator.
 * @constructor
 * @struct
 */
ydn.db.core.AbstractIterator = function() {

};

/**
 * Load cursors.
 * @param {Array.<ydn.db.core.req.AbstractCursor>} cursor
 * @return {!ydn.db.core.req.ICursor} cursor
 */
ydn.db.core.AbstractIterator.prototype.load = goog.abstractMethod;


/**
 *
 * @return {!Array.<string>} return list of store name used for this iterator.
 */
ydn.db.core.AbstractIterator.prototype.stores = goog.abstractMethod;

// Input 31
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Cursor range iterator iterates cursor of an index or an
 * object store.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.IndexIterator');
goog.provide('ydn.db.IndexValueIterator');
goog.provide('ydn.db.Iterator');
goog.provide('ydn.db.Iterator.State');
goog.provide('ydn.db.KeyIterator');
goog.provide('ydn.db.ValueIterator');
goog.require('goog.debug.Logger');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.db.base');
goog.require('ydn.db.core.req.ICursor');
goog.require('ydn.db.core.AbstractIterator');
goog.require('ydn.debug.error.ArgumentException');



/**
 * Create an iterator object.
 * @param {!string} store store name.
 * @param {string=} opt_index store field, where key query is preformed.
 * @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse.
 * @param {boolean=} opt_unique unique.
 * @param {boolean=} opt_key_only true for key only iterator. Default value is
 * true if index is specified, false if not defined.
 * @param {(!Array.<string>|string)=} opt_index_key_path index key path. If
 * key path is specified, key path is used to lookup the index instead of
 * index name.
 * @constructor
 * @extends {ydn.db.core.AbstractIterator}
 * @struct
 */
ydn.db.Iterator = function(store, opt_index, opt_key_range, opt_reverse,
                           opt_unique, opt_key_only, opt_index_key_path) {
  // Note for V8 optimization, declare all properties in constructor.
  if (goog.DEBUG && !goog.isString(store)) {
    throw new TypeError('store name must be a string, but ' + store +
        ' found.');
  }

  /**
   * Store name.
   * @final
   * @private
   */
  this.store_name_ = store;

  /**
   * Indexed field.
   * @final
   * @private
   */
  this.index_name_ = opt_index;
  /**
   * @final
   * @private
   */
  this.index_key_path_ = opt_index_key_path;
  /**
   * @final
   * @private
   */
  this.is_index_iterator_ = !!this.index_name_;
  if (goog.DEBUG) {
    if (goog.isDef(opt_reverse) && !goog.isBoolean(opt_reverse)) {
      throw new ydn.debug.error.ArgumentException('reverse value must be' +
          ' a boolean, but ' + typeof opt_reverse + ' found');
    }
    if (goog.isDef(opt_unique) && !goog.isBoolean(opt_unique)) {
      throw new ydn.debug.error.ArgumentException('unique value must be' +
          ' a boolean, but ' + typeof opt_unique + ' found');
    }
    if (goog.isDef(opt_key_only) && !goog.isBoolean(opt_key_only)) {
      throw new ydn.debug.error.ArgumentException('key_only value must be' +
          ' a boolean, but ' + typeof opt_key_only + ' found');
    }
  }
  /**
   * @final
   * @private
   */
  this.is_key_iterator_ = goog.isDef(opt_key_only) ?
      opt_key_only : !!(goog.isString(this.index_name_));
  var direction = ydn.db.base.Direction.NEXT;
  if (opt_reverse && opt_unique) {
    direction = ydn.db.base.Direction.PREV_UNIQUE;
  } else if (opt_reverse) {
    direction = ydn.db.base.Direction.PREV;
  } else if (opt_unique) {
    direction = ydn.db.base.Direction.NEXT_UNIQUE;
  }

  /**
   * @final
   * @private
   */
  this.direction_ = direction;

  if (goog.DEBUG) {
    var msg = ydn.db.KeyRange.validate(opt_key_range);
    if (msg) {
      throw new ydn.debug.error.ArgumentException('Invalid key range: ' + msg);
    }
  }
  /**
   * @final
   * @private
   */
  this.key_range_ = ydn.db.KeyRange.parseIDBKeyRange(opt_key_range);

  /**
   * cursor state
   * @type {ydn.db.Iterator.State}
   * @private
   */
  this.state_ = ydn.db.Iterator.State.INITIAL;
  /**
   * current effective key.
   * @type {IDBKey|undefined}
   * @private
   */
  this.i_key_;
  /**
   * current primary key.
   * @type {IDBKey|undefined}
   * @private
   */
  this.i_primary_key_;

};
goog.inherits(ydn.db.Iterator, ydn.db.core.AbstractIterator);


/**
 * @define {boolean} to debug this file.
 */
ydn.db.Iterator.DEBUG = false;


/**
 * @type {!string}
 * @private
 */
ydn.db.Iterator.prototype.store_name_;


/**
 * @type {string|undefined}
 * @private
 */
ydn.db.Iterator.prototype.index_name_;


/**
 * Composite index iterator build by using restrict do not have index name,
 * instead index has to be lookup by this index key path. If this is defined,
 * #index_name_ will be undefined and vise versa.
 * @type {!Array.<string>|string|undefined}
 * @private
 */
ydn.db.Iterator.prototype.index_key_path_;


/**
 * @type {boolean}
 * @private
 */
ydn.db.Iterator.prototype.is_index_iterator_;


/**
 *
 * @private
 * @type {IDBKeyRange}
 */
ydn.db.Iterator.prototype.key_range_;


/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.Iterator.prototype.is_key_iterator_ = true;


/**
 * Cursor direction.
 * @type {(ydn.db.base.Direction)}
 * @private
 */
ydn.db.Iterator.prototype.direction_;



/**
 * Create an iterator object.
 * @param {string} store store name.
 * @param {(!KeyRangeJson|ydn.db.KeyRange)=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse.
 * @constructor
 * @extends {ydn.db.Iterator}
 */
ydn.db.KeyIterator = function(store, opt_key_range, opt_reverse) {
  if (arguments.length > 3) {
    throw new ydn.debug.error.ArgumentException('too many argument');
  }
  goog.base(this, store, undefined, opt_key_range, opt_reverse,
      undefined, true);
};
goog.inherits(ydn.db.KeyIterator, ydn.db.Iterator);


/**
 * Create a new key cursor iterator.
 * @param {string} store_name store name.
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.KeyIterator} newly created iterator.
 */
ydn.db.KeyIterator.where = function(store_name, op, value, opt_op2, opt_value2) {
  return new ydn.db.KeyIterator(store_name,
      ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};



/**
 * Create an iterator object.
 * @param {string} store store name.
 * @param {string} index index name.
 * @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse.
 * @param {boolean=} opt_unique unique.
 * @constructor
 * @extends {ydn.db.Iterator}
 */
ydn.db.IndexIterator = function(store, index, opt_key_range, opt_reverse,
                                opt_unique) {
  if (!goog.isString(index)) {
    throw new ydn.debug.error.ArgumentException('index name must be string');
  }
  goog.base(this, store, index, opt_key_range, opt_reverse, opt_unique, true);
};
goog.inherits(ydn.db.IndexIterator, ydn.db.Iterator);


/**
 * Create an iterator object.
 * @param {string} store_name store name.
 * @param {string} index index name.
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.IndexIterator}
 */
ydn.db.IndexIterator.where = function(store_name, index, op, value, opt_op2,
                                opt_value2) {
  return new ydn.db.IndexIterator(store_name, index,
      ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};



/**
 * Create an iterator object.
 * @param {!string} store store name.
 * @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse.
 * @constructor
 * @extends {ydn.db.Iterator}
 */
ydn.db.ValueIterator = function(store, opt_key_range, opt_reverse) {
  if (arguments.length > 3) {
    throw new ydn.debug.error.ArgumentException('too many argument');
  }
  goog.base(this, store, undefined, opt_key_range, opt_reverse, undefined,
      false);
};
goog.inherits(ydn.db.ValueIterator, ydn.db.Iterator);


/**
 * Create a new value cursor range iterator using where clause condition.
 * @param {string} store_name store name.
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.ValueIterator} newly craeted cursor.
 */
ydn.db.ValueIterator.where = function(store_name, op, value, opt_op2,
                                     opt_value2) {
  return new ydn.db.ValueIterator(store_name,
      ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};



/**
 * Create an iterator object.
 * @param {!string} store store name.
 * @param {string} index index name.
 * @param {(KeyRangeJson|ydn.db.KeyRange|IDBKeyRange)=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse.
 * @param {boolean=} opt_unique unique.
 * @constructor
 * @extends {ydn.db.Iterator}
 */
ydn.db.IndexValueIterator = function(store, index, opt_key_range, opt_reverse,
                                    opt_unique) {
  if (!goog.isString(index)) {
    throw new ydn.debug.error.ArgumentException('index name must be string');
  }
  goog.base(this, store, index, opt_key_range, opt_reverse, opt_unique, false);
};
goog.inherits(ydn.db.IndexValueIterator, ydn.db.Iterator);


/**
 *
 * @param {string} store_name store name.
 * @param {string} index index name.
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.IndexValueIterator}
 */
ydn.db.IndexValueIterator.where = function(store_name, index, op, value,
                                           opt_op2, opt_value2) {
  return new ydn.db.IndexValueIterator(store_name, index,
      ydn.db.KeyRange.where(op, value, opt_op2, opt_value2));
};


/**
 * Iterator state.
 * @enum {string}
 */
ydn.db.Iterator.State = {
  INITIAL: 'init',
  WORKING: 'busy',
  RESTING: 'rest',
  COMPLETED: 'done'
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.Iterator.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.Iterator');


/**
 *
 * @return {!string} return store name.
 */
ydn.db.Iterator.prototype.getStoreName = function() {
  return this.store_name_;
};


/**
 *
 * @return {string|undefined} return store name.
 */
ydn.db.Iterator.prototype.getIndexName = function() {
  return this.index_name_;
};


/**
 *
 * @return {ydn.db.base.Direction} return store name.
 */
ydn.db.Iterator.prototype.getDirection = function() {
  return this.direction_;
};


/**
 * This is for friendly module use only, that does not mutate the key range.
 * otherwise use @see #getKeyRange
 * @return {ydn.db.IDBKeyRange} return key range instance.
 */
ydn.db.Iterator.prototype.keyRange = function() {
  return this.key_range_;
};


/**
 *
 * @return {boolean} true if this iterator has key range restriction.
 */
ydn.db.Iterator.prototype.hasKeyRange = function() {
  return !!this.key_range_;
};


/**
 *
 * @return {IDBKey|undefined} get lower value of key range.
 */
ydn.db.Iterator.prototype.getLower = function() {
  return this.key_range_ ? undefined : this.key_range_.lower;
};


/**
 *
 * @return {boolean|undefined} get lower value of key range.
 */
ydn.db.Iterator.prototype.getLowerOpen = function() {
  return this.key_range_ ? undefined : this.key_range_.lowerOpen;
};


/**
 *
 * @return {IDBKey|undefined} get upper value of key range.
 */
ydn.db.Iterator.prototype.getUpper = function() {
  return this.key_range_ ? undefined : this.key_range_.upper;
};


/**
 *
 * @return {boolean|undefined} get upper value of key range.
 */
ydn.db.Iterator.prototype.getUpperOpen = function() {
  return this.key_range_ ? undefined : this.key_range_.upperOpen;
};


/**
 * @return {ydn.db.IDBKeyRange} return a clone of key range.
 */
ydn.db.Iterator.prototype.getKeyRange = function() {
  if (this.key_range_) {
    if (this.key_range_ instanceof ydn.db.IDBKeyRange) {
      return this.key_range_; // none mutable key range.
    } else {
      return ydn.db.IDBKeyRange.bound(this.key_range_.lower,
          this.key_range_.upper, this.key_range_.lowerOpen,
          this.key_range_.upperOpen);
    }
  } else {
    return null;
  }
};


/**
 * @return {boolean} <code>true</code> if key iterator, <code>false</code>
 * if value iterator.
 */
ydn.db.Iterator.prototype.isKeyIterator = function() {
  return this.is_key_iterator_;
};


/**
 *
 * @return {boolean} true if index iterator.
 */
ydn.db.Iterator.prototype.isIndexIterator = function() {
  return this.is_index_iterator_;
};


/**
 * @return {boolean} true for primary iterator, which use primary key as
 * effective key.
 * @deprecated use isIndexIterator instead.
 */
ydn.db.Iterator.prototype.isPrimaryIterator = function() {
  return !this.is_index_iterator_;
};


/**
 * Copy this iterator with internal state.
 * @see copy
 * @return {!ydn.db.Iterator}
 */
ydn.db.Iterator.prototype.clone = function() {
  return new ydn.db.Iterator(this.store_name_, this.index_name_,
      this.key_range_, this.isReversed(), this.isUnique(), this.isKeyIterator(),
      this.index_key_path_);
};


/**
 * Set unique state.
 * @param {boolean} val
 * @return {!ydn.db.Iterator} newly created iterator.
 */
ydn.db.Iterator.prototype.unique = function(val) {
  return new ydn.db.Iterator(this.store_name_, this.index_name_,
      this.key_range_, this.isReversed(), val, this.isKeyIterator(),
      this.index_key_path_);
};


/**
 * Copy this iterator.
 * @see clone
 * @return {!ydn.db.Iterator}
 */
ydn.db.Iterator.prototype.copy = function() {
  return new ydn.db.Iterator(this.store_name_, this.index_name_,
      this.key_range_, this.isReversed(), this.isUnique(), this.isKeyIterator(),
      this.index_key_path_);
};


if (goog.DEBUG) {

  /**
   * @inheritDoc
   */
  ydn.db.Iterator.prototype.toJSON = function() {
    return {
      'store': this.store_name_,
      'index': this.index_name_,
      'keyRange': this.key_range_ ?
          ydn.db.KeyRange.toJSON(this.key_range_) : null,
      'direction': this.direction_
    };
  };

  /**
   * @override
   */
  ydn.db.Iterator.prototype.toString = function() {
    var str = goog.isDef(this.index_key_path_) ?
        ':' + this.index_key_path_.join(',') :
            goog.isDef(this.index_name_) ? ':' + this.index_name_ : '';
    str += ydn.db.KeyRange.toString(this.key_range_);
    if (this.state_ != ydn.db.Iterator.State.INITIAL) {
      str += this.state_ + '{' + this.i_key_;
      if (this.isIndexIterator()) {
        str += ', ' + this.i_primary_key_;
      }
      str += '}';
    }
    var s = this.isIndexIterator() ? 'Index' : '';
    s += this.isKeyIterator() ? 'Key' : 'Value';
    return s + 'Iterator:' + this.store_name_ + str;
  };
}


/**
 * Resume from a saved position.
 * @param {IDBKey} key effective key as start position.
 * @param {IDBKey=} opt_primary_key primary key as start position for index
 * iterator.
 * @return {ydn.db.Iterator}
 */
ydn.db.Iterator.prototype.resume = function(key, opt_primary_key) {
  var iter = new ydn.db.Iterator(this.store_name_, this.index_name_,
      this.key_range_, this.isReversed(), this.isUnique(),
      this.is_key_iterator_, this.index_key_path_);
  throw new Error('not possible');
};


/**
 * Resume from a saved position.
 * @param {IDBKey=} opt_key effective key as start position.
 * @param {IDBKey=} opt_primary_key primary key as start position for index
 * iterator.
 * @return {!ydn.db.Iterator}
 */
ydn.db.Iterator.prototype.reverse = function(opt_key, opt_primary_key) {
  var iter = new ydn.db.Iterator(this.store_name_, this.index_name_,
      this.key_range_, !this.isReversed(), this.isUnique(),
      this.is_key_iterator_, this.index_key_path_);
  //if (this.cursor_) {
    //console.log('rev cur')
    //iter.cursor_ = this.cursor_.clone(true);
  //}
  return iter;
};


/**
 *
 * @return {boolean} true if iteration direction is reverse.
 */
ydn.db.Iterator.prototype.isReversed = function() {
  return this.direction_ === ydn.db.base.Direction.PREV ||
      this.direction_ === ydn.db.base.Direction.PREV_UNIQUE;
};


/**
 *
 * @return {boolean} true if cursor is iterator unique key.
 */
ydn.db.Iterator.prototype.isUnique = function() {
  return this.direction_ === ydn.db.base.Direction.NEXT_UNIQUE ||
      this.direction_ === ydn.db.base.Direction.PREV_UNIQUE;
};


/**
 * Return next key range.
 * @return {IDBKeyRange}
 */
ydn.db.Iterator.prototype.getNextKeyRange = function() {
  if (this.state_ == ydn.db.Iterator.State.COMPLETED ||
      this.state_ == ydn.db.Iterator.State.INITIAL ||
      !goog.isDefAndNotNull(this.i_key_)) {
    return this.key_range_;
  } else {
    return this.key_range_;
  }
};


/**
 *
 * @return {ydn.db.Iterator.State} iterator state.
 */
ydn.db.Iterator.prototype.getState = function() {
  return this.state_;
};


/**
 * @inheritDoc
 */
ydn.db.Iterator.prototype.load = function(cursors) {
  var cursor = cursors[0];
  cursor.init(this.store_name_,
      this.index_key_path_ || this.index_name_,
      this.key_range_, this.direction_, this.is_key_iterator_);
  this.state_ = ydn.db.Iterator.State.WORKING;
  var me = this;
  cursor.onTerminated = function(is_existed, key, primary_key) {
    me.i_key_ = key;
    me.i_primary_key_ = primary_key;
    me.state_ = is_existed ? ydn.db.Iterator.State.RESTING :
        ydn.db.Iterator.State.COMPLETED;
  };
  cursor.openCursor(this.i_key_, this.i_primary_key_);
  return cursor;
};


/**
 * @return {boolean} return true if iterator is fresh state, i.e., iteration
 * will start without starting point.
 */
ydn.db.Iterator.prototype.isFreshState = function() {
  return this.state_ == ydn.db.Iterator.State.INITIAL ||
      this.state_ == ydn.db.Iterator.State.COMPLETED;
};


/**
 *
 * @return {IDBKey|undefined} Current cursor key.
 */
ydn.db.Iterator.prototype.getKey = function() {
  return this.i_key_;
};


/**
 *
 * @return {IDBKey|undefined} Current cursor index key.
 */
ydn.db.Iterator.prototype.getPrimaryKey = function() {
  return this.i_primary_key_;
};


/**
 * Reset the state.
 * @param {ydn.db.Iterator.State=} opt_state reset state, default to INITIAL.
 * @param {IDBKey=} opt_key effective key for setting RESTING state.
 * @param {IDBKey=} opt_primary_key effective key for setting RESTING state.
 */
ydn.db.Iterator.prototype.reset = function(opt_state,
                                           opt_key, opt_primary_key) {
  var state = opt_state || ydn.db.Iterator.State.INITIAL;
  if (this.getState() == ydn.db.Iterator.State.WORKING) {
    this.logger.warning(this + ': resetting state to ' + state +
        ' ignore during iteration');
  } else {
    this.i_key_ = opt_key;
    this.i_primary_key_ = opt_primary_key;
    this.state_ = state;
  }
};


/**
 *
 * @return {!Array.<string>} list of stores.
 */
ydn.db.Iterator.prototype.stores = function() {
  return [this.store_name_];
};


// Input 32
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Provide package variables.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db');
goog.require('ydn.db.utils');


/**
 *
 * @define {string} version string.
 */
ydn.db.version = '0';


/**
 * IDBFactory.cmp with fallback for websql.
 * @type {function(*, *): number} returns 1 if the first key is
 * greater than the second, -1 if the first is less than the second, and 0 if
 * the first is equal to the second.
 */
ydn.db.cmp = (ydn.db.base.indexedDb &&
    ydn.db.base.indexedDb.cmp) ?
    goog.bind(ydn.db.base.indexedDb.cmp,
        ydn.db.base.indexedDb) : ydn.db.utils.cmp;


/**
 * Inject handler for deleting database by storage mechanisms.
 * @type {Array.<function(string, string=): (ydn.db.Request|undefined)>}
 */
ydn.db.databaseDeletors = [];


/**
 * Delete database. This will attempt to delete in all mechanisms.
 * @param {string} db_name name of database.
 * @param {string=} opt_type delete only specific types.
 * @return {!ydn.db.Request}
 */
ydn.db.deleteDatabase = function(db_name, opt_type) {
  var df;
  for (var i = 0; i < ydn.db.databaseDeletors.length; i++) {
    var req = ydn.db.databaseDeletors[i](db_name, opt_type);
    if (req) {
      df = req;
    }
  }
  return df || ydn.db.Request.succeed(ydn.db.Request.Method.VERSION_CHANGE,
      null);
};



// Input 33
/**
 * @fileoverview Cursor stream interface.
 *
 * Push key to seek method. This will pop the result to sink function. It
 * is set during initialization.
 *
 * User: kyawtun
 * Date: 11/11/12
 */

goog.provide('ydn.db.con.ICursorStream');



/**
 *
 * @interface
 */
ydn.db.con.ICursorStream = function() {};


/**
 * Request to seek to a key. This will pop the result to sink function. It
 * is set during initialization.
 * @param {*} key
 */
ydn.db.con.ICursorStream.prototype.seek = goog.abstractMethod;


/**
 * Invoke callback when all stack are completely fetched.
 * @param {Function} callback
 */
ydn.db.con.ICursorStream.prototype.onFinish = goog.abstractMethod;
// Input 34
/**
 * @fileoverview Interface for database service provider.
 */


goog.provide('ydn.db.con.IStorage');
goog.require('goog.async.Deferred');



/**
 * @interface
 */
ydn.db.con.IStorage = function() {};


/**
 * Close the connection.
 */
ydn.db.con.IStorage.prototype.close = goog.abstractMethod;


/**
 * Run a transaction.
 * @param {function((!IDBTransaction|!SQLTransaction|Object))|!Function} trFn
 * function that invoke in the transaction.
 * @param {!Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=}
  * completed_event_handler handler for completed event.
 */
ydn.db.con.IStorage.prototype.transaction = goog.abstractMethod;





// Input 35
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Cursor stream accept pirmary key and pop reference value to
 * a sink.
 *
 * User: kyawtun
 * Date: 11/11/12
 */

goog.provide('ydn.db.con.IdbCursorStream');
goog.require('goog.debug.Logger');
goog.require('ydn.db.con.ICursorStream');
goog.require('ydn.db.con.IStorage');



/**
 *
 * @param {!ydn.db.con.IStorage|!IDBTransaction} db
 * @param {string} store_name store name.
 * @param {string|undefined} index_name index name.
 * @param {Function} sink to receive value.
 * @constructor
 * @implements {ydn.db.con.ICursorStream}
 */
ydn.db.con.IdbCursorStream = function(db, store_name, index_name, sink) {
  if ('transaction' in db) {
    this.db_ = /** @type {ydn.db.con.IStorage} */ (db);
    this.idb_ = null;
    this.tx_ = null;
  } else if ('objectStore' in db) { //  IDBTransaction
    var tx = /** @type {IDBTransaction} */ (db);
    this.db_ = null;
    this.idb_ = tx.db;
    this.tx_ = tx;
    if (goog.DEBUG && !this.tx_.db.objectStoreNames.contains(store_name)) {
      throw new ydn.error.ArgumentException('store "' + store_name +
          '" not in transaction.');
    }
  } else {
    throw new ydn.error.ArgumentException('storage instance require.');
  }

  this.store_name_ = store_name;
  this.index_name_ = index_name;
  this.sink_ = sink;
  this.cursor_ = null;
  this.stack_ = [];
  this.running_ = 0;
  this.on_tx_request_ = false;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.con.IdbCursorStream.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.con.IdbCursorStream');


/**
 * @type {ydn.db.con.IStorage}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.db_;


/**
 * @type {IDBTransaction}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.tx_;


/**
 * @type {IDBDatabase}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.idb_;


/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.on_tx_request_ = false;


/**
 * @type {string}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.store_name_;


/**
 * @type {string|undefined}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.index_name_;


/**
 *
 * @type {!Array}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.stack_ = [];


/**
 * @type {Function}
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.sink_;


/**
 *
 * @return {boolean}
 */
ydn.db.con.IdbCursorStream.prototype.isIndex = function() {
  return goog.isDefAndNotNull(this.index_name_);
};


/**
 * Read cursor.
 * @param {!IDBRequest} req
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.processRequest_ = function(req) {

  // here very careful with circular dependency.
  // req object is own by transaction which in turn own by the browser (global)
  // we don't want to keep reference to req.
  // we keep reference to cursor instead.
  // even if we don't keep the req, this req.onsuccess and req.onerror callbacks
  // are still active when cursor invoke advance method.

  this.running_ ++;
  var me = this;
  req.onsuccess = function(ev) {
    var cursor = ev.target.result;
    if (cursor) {
      if (goog.isFunction(me.sink_)) {
        //console.log(cursor);
        var cursor_value = cursor['value'];
        var value = me.isIndex() ? cursor_value[me.index_name_] : cursor_value;
        me.sink_(cursor.primaryKey, value);
      } else {
        me.logger.warning('sink gone, dropping value for: ' +
            cursor.primaryKey);
      }
      if (cursor && me.stack_.length > 0) {
        cursor['continue'](me.stack_.shift());
      } else {
        me.running_ --;
        me.clearStack_();
      }
    }
  };
  req.onerror = function(ev) {
    var msg = 'error' in req ?
        req['error'].name + ':' + req['error'].message : '';
    me.logger.warning('seeking fail. ' + msg);
    me.running_ --;
    me.clearStack_();
  };
};


/**
 * Collect result.
 * @param {Function} callback
 */
ydn.db.con.IdbCursorStream.prototype.onFinish = function(callback) {
  if (this.stack_.length == 0 && this.running_ == 0) {
    callback(); // we have nothing.
  } else {
    this.collector_ = callback;
  }
};


/**
 * @private
 */
ydn.db.con.IdbCursorStream.prototype.createRequest_ = function() {

  if (this.on_tx_request_) {
    return; // else: we should not request more than on transaction request
  }


  var me = this;
  var on_completed = function(type, ev) {
    me.tx_ = null;
    if (type !== ydn.db.base.TxEventTypes.COMPLETE) {
      me.logger.warning(ev.name + ':' + ev.message);
    }
    me.logger.finest(me + ' transaction ' + type);
  };

  /**
   *
   * @param {IDBTransaction} tx active tx.
   */
  var doRequest = function(tx) {
    var key = me.stack_.shift();
    me.logger.finest(me + ' transaction started for ' + key);
    var store = tx.objectStore(me.store_name_);
    /**
     * We cannot use index here, because index is useful to loopup from
     * index key to primary key. Here we need to lookup from primary key
     * to index key.
     */
//    if (goog.isString(me.index_name_)) {
//      var indexNames = /** @type {DOMStringList} */ (store['indexNames']);
//      if (goog.DEBUG && !indexNames.contains(me.index_name_)) {
//        throw new ydn.db.InvalidStateError('object store ' + me.store_name_ +
//            ' does not have require index ' + me.index_name_);
//      }
//      var index = store.index(me.index_name_);
//      me.processRequest_(index.openKeyCursor(key));
//    } else {
      // as of v1, ObjectStore do not have openKeyCursor method.
      // filed bug on:
      // http://lists.w3.org/Archives/Public/public-webapps/2012OctDec/0466.html
      me.processRequest_(store.openCursor(key));
    //}
  };

  if (this.tx_) {
    me.logger.finest(me + ' using existing tx.');
    doRequest(this.tx_);
  } else if (this.idb_) {
    me.logger.finest(me + ' creating tx from IDBDatabase.');
    this.tx = this.idb_.transaction([this.store_name_],
        ydn.db.base.TransactionMode.READ_ONLY);
    this.tx.oncomplete = function(event) {
      on_completed(ydn.db.base.TxEventTypes.COMPLETE, event);
    };

    this.tx.onerror = function(event) {
      on_completed(ydn.db.base.TxEventTypes.ERROR, event);
    };

    this.tx.onabort = function(event) {
      on_completed(ydn.db.base.TxEventTypes.ABORT, event);
    };
  } else if (this.db_) {
    me.logger.finest(me + ' creating tx from ydn.db.con.IStorage.');
    this.on_tx_request_ = true;
    this.db_.transaction(function(tx) {
      me.on_tx_request_ = false;
      //console.log(tx)
      doRequest(tx);
    }, [me.store_name_], ydn.db.base.TransactionMode.READ_ONLY, on_completed);
  } else {
    var msg = goog.DEBUG ? 'no way to create a transaction provided.' : '';
    throw new ydn.error.InternalError(msg);
  }

};


ydn.db.con.IdbCursorStream.prototype.clearStack_ = function() {
  if (this.cursor_ && this.stack_.length > 0) {
    // we retain only valid request with active cursor.
    this.cursor_['continue'](this.stack_.shift());
  } else {
    if (this.running_ == 0) {
      if (this.collector_) {
        this.collector_();
      }
    }
  }
};


/**
 * Request to seek to a key.
 * @param key
 */
ydn.db.con.IdbCursorStream.prototype.seek = function (key) {
  this.stack_.push(key);

  this.createRequest_();

};


// Input 36
/**
 * @fileoverview Cursor stream accept key and pop to a sink.
 *
 * User: kyawtun
 * Date: 11/11/12
 */

goog.provide('ydn.db.Streamer');
goog.require('ydn.db.con.IdbCursorStream');
goog.require('ydn.db.con.IStorage');
goog.require('ydn.db.Iterator');
goog.require('ydn.debug.error.ArgumentException');


/**
 *
 * @param {ydn.db.con.IStorage|ydn.db.base.Transaction} storage storage connector.
 * @param {string} store_name store name.
 * @param {string=} field_name projection field name.
 * @constructor
 */
ydn.db.Streamer = function(storage, store_name, field_name) {

  if (storage && storage instanceof ydn.db.con.Storage) {
    this.db_ = /** @type {ydn.db.con.IStorage} */ (storage);
    this.cursor_ = null;
  } else if (storage && storage.db) {
    var tx = /** @type {!IDBTransaction} */ (storage);
    this.db_ = null;
    this.setTx(tx);
  } else {
    throw new ydn.debug.error.ArgumentException(
        'ydn.db.Streamer: First argument requires storage or transaction ' +
            'instance required.');
  }

  if (!goog.isString(store_name)) {
    throw new ydn.debug.error.ArgumentException('a store name required.');
  }
  this.store_name_ = store_name;
  if (goog.isDef(field_name) && !goog.isString(field_name)) {
    throw new ydn.debug.error.ArgumentException('index name must be a string.');
  }
  this.index_name_ = field_name;

  this.cursor_ = null;
  this.stack_value_ = [];
  this.stack_key_ = [];
  this.is_collecting_ = false;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.Streamer.prototype.logger =
  goog.debug.Logger.getLogger('ydn.db.Streamer');


/**
 *
 * @type {ydn.db.con.IStorage}
 * @private
 */
ydn.db.Streamer.prototype.db_ = null;

/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.Streamer.prototype.key_only_ = true;


/**
 *
 * @type {ydn.db.con.Storage}
 * @private
 */
ydn.db.Streamer.prototype.storage_ = null;


/**
 * @type {string}
 * @private
 */
ydn.db.Streamer.prototype.store_name_;


/**
 * @type {string|undefined}
 * @private
 */
ydn.db.Streamer.prototype.index_name_;


/**
 *
 * @type {(function(*, *, Function): boolean)?}
 */
ydn.db.Streamer.prototype.sink_ = null;


/**
 * @private
 * @type {Array}
 */
ydn.db.Streamer.prototype.stack_key_ = [];


/**
 * @private
 * @type {Array}
 */
ydn.db.Streamer.prototype.stack_value_ = [];


/**
 *
 * @return {string|undefined}
 */
ydn.db.Streamer.prototype.getFieldName = function() {
  return this.index_name_;
};


/**
 *
 * @type {ydn.db.con.ICursorStream}
 * @private
 */
ydn.db.Streamer.prototype.cursor_ = null;


/**
 *
 * @return {boolean}
 */
ydn.db.Streamer.prototype.isKeyIterator = function() {
  return this.key_only_;
};


/**
 *
 * @param {function(*, *, Function): boolean} sink
 */
ydn.db.Streamer.prototype.setSink = function(sink) {
  this.sink_ = sink;
};


/**
 *
 * @param {ydn.db.base.Transaction} tx
 * transaction.
 */
ydn.db.Streamer.prototype.setTx = function(tx) {
  if (tx.db) {
    var idb_tx = /** @type {!IDBTransaction} */ (tx);
    this.cursor_ = new ydn.db.con.IdbCursorStream(idb_tx,
        this.store_name_, this.index_name_, goog.bind(this.collector_, this));
  } else {
    throw new ydn.debug.error.ArgumentException(
        'Invalid IndexedDB Transaction.');
  }

};


/**
 * Push the result because a result is ready. This will push until stack
 * is empty.
 * @private
 */
ydn.db.Streamer.prototype.push_ = function() {
  var on_queue = this.stack_value_.length > 0;
  if (on_queue && !this.is_collecting_ && goog.isFunction(this.sink_)) {

    var me = this;
    var waiter = function() {
      me.push_();
    };
    var key = this.stack_key_.shift();
    var value = this.stack_value_.shift();
    on_queue = this.stack_value_.length > 0;
    var to_wait = this.sink_(key, value, on_queue ? waiter : null);
    if (on_queue && !to_wait) {
      this.push_();
    }
  }

};


/**
 *
 * @type {boolean} Flag to indicate collection.
 * @private
 */
ydn.db.Streamer.prototype.is_collecting_ = false;


/**
 * Collect results.
 * During collecting results, sink will not pop to the result. Pushing is
 * disable.
 * @param {Function} callback a callback function to receive the result array.
 * @throws ydn.ArgumentException if sink function is set.
 */
ydn.db.Streamer.prototype.collect = function(callback) {
  if (this.cursor_) {
    this.is_collecting_ = true;
    var me = this;
    this.cursor_.onFinish(function on_finish(e) {
      callback(me.stack_key_, me.stack_value_);
      me.stack_key_ = [];
      me.stack_value_ = [];
      me.is_collecting_ = false;
    });
  } else {
    // throw new ydn.error.InvalidOperationError('Not collected.');
    // this.logger.warning('Not collected yet.');
    callback(this.stack_key_, this.stack_value_);
    this.stack_key_ = [];
    this.stack_value_ = [];
  }

};


/**
 * Collect value from cursor stream.
 * @param {*} key
 * @param {*} value
 * @private
 */
ydn.db.Streamer.prototype.collector_ = function(key, value) {
  this.stack_key_.push(key);
  this.stack_value_.push(value);
  this.push_();
};


/**
 * Push a key.
 * @param {*} key key to seek the value.
 * @param {*=} value if already got the value.
 * @throws {ydn.error.InvalidOperationError}
 */
ydn.db.Streamer.prototype.push = function(key, value) {
  if (this.is_collecting_) {
    var msg = goog.DEBUG ? 'push not allowed after a collection is started' : '';
    throw new ydn.error.InvalidOperationError(msg);
  }
  if (arguments.length >= 2) {
    this.collector_(key, value);
  } else {
    // we have to create cursor_ object lazily because, at the time of
    // instantiation, database may not have connected yet.
    if (!this.cursor_) {
      if (!this.db_) {
        var msg2 = goog.DEBUG ? 'Database is not setup.' : '';
        throw new ydn.error.InvalidOperationError(msg2);
      }
      var type = this.db_.getType();
      if (!type) {
        var msg3 = goog.DEBUG ? 'Database is not connected.' : '';
        throw new ydn.error.InvalidOperationError(msg3);
      } else if (type === ydn.db.base.Mechanisms.IDB) {
        this.cursor_ = new ydn.db.con.IdbCursorStream(this.db_,
          this.store_name_, this.index_name_,
            goog.bind(this.collector_, this));
      } else {
        throw new ydn.error.NotImplementedException(type);
      }
    }

    this.cursor_.seek(key);
  }
};


/**
 * Extract key from the parent iterator and push.
 * @param key
 * @param value
 */
ydn.db.Streamer.prototype.pull = function(key, value) {

  if (!goog.isDef(this.foreign_key_index_name_)) {
    this.push(key);
  } else if (this.key_only_) {
    this.push(value); // index key
  } else {
    if (goog.isDefAndNotNull(value)) {
      this.push(value[this.foreign_key_index_name_]);
    } else {
      this.push(undefined, undefined);
    }

  }
};


/**
 * @type {string}
 * @private
 */
ydn.db.Streamer.prototype.foreign_key_store_name_;


/**
 * @type {string|undefined}
 * @private
 */
ydn.db.Streamer.prototype.foreign_key_index_name_;


/**
 *
 * @param {string} store_name
 * @param {string=} index_name
 */
ydn.db.Streamer.prototype.setRelation = function(store_name, index_name) {
  this.foreign_key_store_name_ = store_name;
  this.foreign_key_index_name_ = index_name;
};


/**
 * Both of them may be undefined.
 * @return {!Array.<string>} return store_name and index_name.
 */
ydn.db.Streamer.prototype.getRelation = function() {
  return [this.foreign_key_store_name_, this.foreign_key_index_name_];
};


/**
 *
 * @return {string} return store name.
 */
ydn.db.Streamer.prototype.getStoreName = function() {
  return this.store_name_;
};


/**
 *
 * @return {string|undefined} return store name.
 */
ydn.db.Streamer.prototype.getIndexName = function() {
  return this.index_name_;
};


if (goog.DEBUG) {
  /**
   * @override
   */
  ydn.db.Streamer.prototype.toString = function() {
    return 'Streamer:' + this.store_name_ + (this.index_name_ || '');
  };
}


// Input 37
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Abstract join algorithm.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.algo.AbstractSolver');
goog.require('goog.debug.Logger');
goog.require('ydn.db');
goog.require('ydn.db.Streamer');



/**
 *
 * @param {(!Array|!{push: Function}|!ydn.db.Streamer)=} out output receiver.
 * @param {number=} opt_limit limit.
 * to algorithm input and output.
 * @constructor
 */
ydn.db.algo.AbstractSolver = function(out, opt_limit) {
  if (goog.DEBUG && goog.isDefAndNotNull(out) && !('push' in out)) {
    throw new ydn.error.ArgumentException('output receiver object must have ' +
        '"push" method.');
  }
  this.out = out || null;
  this.limit = opt_limit;
  this.match_count = 0;
  /**
   * @protected
   * @type {boolean}
   */
  this.is_reverse = false;
};


/**
 * Return list of iterators for scanning for managed solver.
 * @return {Array.<!ydn.db.Iterator>} iterators.
 */
ydn.db.algo.AbstractSolver.prototype.getIterators = function() {
  return null;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.algo.AbstractSolver.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.algo.AbstractSolver');


/**
 * Invoke before beginning of the iteration process.
 *
 * @param {!Array} iterators list of iterators feed to the scanner.
 * @param {!Function} callback on finish callback function.
 * @return {boolean}
 */
ydn.db.algo.AbstractSolver.prototype.begin = function(iterators, callback) {
  this.is_reverse = iterators[0].isReversed();
  if (goog.DEBUG) {
    for (var i = 0; i < iterators.length; i++) {
      if (!(iterators[i] instanceof ydn.db.Iterator)) {
        throw new ydn.debug.error.TypeError('item at iterators ' + i +
            ' is not an iterator.');
      }
      if (i > 0) {
        if (this.is_reverse != iterators[i].isReversed()) {
          var r = this.is_reverse ? 'be reverse' : 'not be reverse';
          throw new ydn.debug.error.TypeError('iterator at ' + i +
              ' must ' + r);
        }
      }
    }
  }
  var s = '{';
  for (var i = 0; i < iterators.length; i++) {
    if (i > 0) {
      s += ', '
    }
    s += iterators.toString();
  }
  s += '}';
  if (this.is_reverse) {
    s += ' reverse';
  }
  this.logger.fine(this + ' begin ' + s);
  return false;
};


/**
 * Push the result if all keys match. Break the limit if the number of results
 * reach the limit.
 * @param {!Array} advance
 * @param {!Array} keys input values.
 * @param {!Array} values output values.
 * @param {*=} opt_match_key match key.
 * @return {!Object} cursor advancement array.
 * @protected
 */
ydn.db.algo.AbstractSolver.prototype.pusher = function(advance, keys, values,
                                                       opt_match_key) {

  var matched = goog.isDefAndNotNull(opt_match_key);
  if (!goog.isDef(opt_match_key)) {
    opt_match_key = values[0];
    matched = goog.isDefAndNotNull(opt_match_key);
    for (var i = 1; matched && i < values.length; i++) {
      if (!goog.isDefAndNotNull(values[i]) ||
          ydn.db.cmp(values[i], opt_match_key) != 0) {
        matched = false;
      }
    }
  }

  if (matched) {
    this.match_count++;
    //console.log(['match key', match_key, JSON.stringify(keys)]);
    if (this.out) {
      this.out.push(opt_match_key);
    }
    if (goog.isDef(this.limit) && this.match_count >= this.limit) {
      return [];
    }
  }

  return advance;
};


/**
 *
 * @param {!Array} input input values.
 * @param {!Array} output output values.
 * @return {!Array|!Object} next positions.
 */
ydn.db.algo.AbstractSolver.prototype.solver = function(input, output) {
  return [];
};


/**
 * Invoke at the end of the iteration process.
 * @param {!Function} callback on finish callback function.
 * @return {boolean} true to wait.
 */
ydn.db.algo.AbstractSolver.prototype.finish = function(callback) {
  return false;
};

// Input 38
/**
 *
 * @fileoverview Error classes for the database module.
 *
 */

goog.provide('ydn.db.ConstraintError');
goog.provide('ydn.db.InternalError');
goog.provide('ydn.db.InvalidKeyException');
goog.provide('ydn.db.InvalidStateError');
goog.provide('ydn.db.NotFoundError');
goog.provide('ydn.db.ScopeError');
goog.provide('ydn.db.SecurityError');
goog.provide('ydn.db.TimeoutError');
goog.provide('ydn.db.VersionError');



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.ConstraintError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.ConstraintError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ConstraintError';
};
goog.inherits(ydn.db.ConstraintError, Error);


/**
 *
 * @type {string}
 */
ydn.db.ConstraintError.prototype.name = 'ConstraintError';


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.ConstraintError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.InvalidKeyException = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.InvalidKeyException);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.db.InvalidKeyException';
};
goog.inherits(ydn.db.InvalidKeyException, Error);


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.InvalidKeyException.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.VersionError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.VersionError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.db.VersionError';
};
goog.inherits(ydn.db.VersionError, Error);


/**
 * @type {string} name of error.
 */
ydn.db.VersionError.prototype.name = 'ydn.db.VersionError';


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.VersionError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.InternalError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.InternalError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
};
goog.inherits(ydn.db.InternalError, Error);


/**
 * @type {string} name of error.
 */
ydn.db.InternalError.prototype.name = 'ydn.db.InternalError';



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.ScopeError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.ScopeError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.db.ScopeError';
};
goog.inherits(ydn.db.ScopeError, Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.InvalidStateError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.InvalidStateError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'InvalidStateError';
};
goog.inherits(ydn.db.InvalidStateError, Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.InvalidAccessError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.InvalidAccessError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'InvalidAccessError';
};
goog.inherits(ydn.db.InvalidAccessError, Error);



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.NotFoundError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.NotFoundError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'NotFoundError';
};
goog.inherits(ydn.db.NotFoundError, Error);


/**
* @type {string} name of error.
*/
ydn.db.NotFoundError.prototype.name = 'NotFoundError';


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.NotFoundError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.db.DataCloneError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.DataCloneError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'DataCloneError';
};
goog.inherits(ydn.db.DataCloneError, Error);


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.DataCloneError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 *
 * @param {SQLError} e original error.
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {Error}
 */
ydn.db.SQLError = function(e, opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.SQLError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.message += ' :' + e.message + ' [' + e.code + ']';
  this.name = 'SQLError';
};
goog.inherits(ydn.db.SQLError, Error);



if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.SQLError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 *
 * @param {Error} e original message.
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {Error}
 */
ydn.db.SecurityError = function(e, opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.SecurityError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.message += ' :' + e.message;
  this.name = 'SecurityError';
};
goog.inherits(ydn.db.SecurityError, Error);


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.SecurityError.prototype.toString = function() {
    return this.name + ': ' + this.message;
  };
}



/**
 *
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {Error}
 */
ydn.db.SqlParseError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.SqlParseError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.db.SqlParseError';
};
goog.inherits(ydn.db.SqlParseError, Error);



/**
 *
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {Error}
 */
ydn.db.TimeoutError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.TimeoutError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.db.TimeoutError';
};
goog.inherits(ydn.db.TimeoutError, Error);



/**
 * @param {*} result request result.
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {Error}
 */
ydn.db.TxError = function(result, opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.db.TxError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'TxError';
  this.result = result;
};
goog.inherits(ydn.db.TxError, Error);


/**
 * @type {*}
 */
ydn.db.TxError.prototype.result;


/**
 * @return {*} request result.
 */
ydn.db.TxError.prototype.getResult = function() {
  return this.result;
};



/**
 *
 * @param {*} result request result.
 * @param {*=} opt_msg optional message.
 * @constructor
 * @extends {ydn.db.TxError}
 */
ydn.db.TxAbortedError = function(result, opt_msg) {
  goog.base(this, result, opt_msg);
  this.name = 'TxAbortedError';
};
goog.inherits(ydn.db.TxAbortedError, ydn.db.TxError);




// Input 39
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview  A unique key for a datastore object supporting hierarchy of
 * parent-child relationships for a record.
 *
 * The instances are immutable.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.Key');



/**
 * Builds a new Key object of known id.
 *
 * @param {string|!ydn.db.Key.Json} store_or_json_or_value store name of key
 * object in JSON format.
 * @param {!IDBKey=} opt_id key id.
 * @param {ydn.db.Key=} opt_parent optional parent key.
 * @constructor
 */
ydn.db.Key = function(store_or_json_or_value, opt_id, opt_parent) {

  var store_name;
  if (goog.isObject(store_or_json_or_value)) {
    store_name = store_or_json_or_value['store'];
    opt_id = store_or_json_or_value['id'];
    if (goog.isDefAndNotNull(store_or_json_or_value['parent'])) {
      opt_parent = new ydn.db.Key(store_or_json_or_value['parent']);
    }
  } else {
    goog.asserts.assertString(store_or_json_or_value, 'store name of' +
        ' a key object must be a string');
    if (!goog.isDef(opt_id)) {
      // must be valueOf string
      var idx = store_or_json_or_value.lastIndexOf(ydn.db.Key.SEP_PARENT);
      /**
       * @type {string}
       */
      var store_and_id = store_or_json_or_value;
      if (idx > 0) {
        store_and_id = store_or_json_or_value.substr(idx);
        opt_parent = new ydn.db.Key(store_or_json_or_value.substring(0, idx));
      }
      var parts = store_and_id.split(ydn.db.Key.SEP_STORE);
      store_name = parts[0];
      opt_id = parts[1];
      if (!goog.isDef(opt_id)) {
        throw Error('Invalid key value: ' + store_or_json_or_value);
      }
    } else {
      store_name = store_or_json_or_value;
    }
  }

  /**
   * @final
   */
  this.store_name = store_name;
  /**
   * @final
   */
  this.id = opt_id;
  /**
   * @final
   */
  this.parent = opt_parent || null;

};


/**
 * @typedef {number|string|!Date|!Array.<number|string|!Date>}
 */
var IDBKey;


/**
 * @typedef {{
 *  store: string,
 *  id: (string|number),
 *  parent: (ydn.db.Key|undefined)
 * }}
 */
ydn.db.Key.Json;


/**
 * @private
 * @type {!IDBKey}
 */
ydn.db.Key.prototype.id;


/**
 * @private
 * @type {string}
 */
ydn.db.Key.prototype.store_name;


/**
 * @private
 * @type {ydn.db.Key?}
 */
ydn.db.Key.prototype.parent;


/**
 * @return {!Object} key in JSON object.
 */
ydn.db.Key.prototype.toJSON = function() {
  var obj = {
    'store': this.store_name,
    'id': this.id
  };
  if (this.parent) {
    obj['parent'] = this.parent.toJSON();
  }
  return obj;
};


/**
 * Separator between child and parent.
 * @const
 * @type {string} seperator string.
 */
ydn.db.Key.SEP_PARENT = '^|';

/**
 * Separator between table and key.
 * @const {string}
 */
ydn.db.Key.SEP_STORE = '^:';


/**
 * @override
 * @return {string} seperator string.
 */
ydn.db.Key.prototype.valueOf = function() {
  // necessary to make web-safe string ?
  var parent_value = this.parent ? this.parent.valueOf() +
      ydn.db.Key.SEP_PARENT : '';
  return parent_value + this.store_name + ydn.db.Key.SEP_STORE + this.id;
};


/**
 * @inheritDoc
 */
ydn.db.Key.prototype.toString = function() {
  return this.valueOf().replace('^|', '|').replace('^:', ':');
};


/**
 *
 * @return {string} return store name.
 */
ydn.db.Key.prototype.getStoreName = function() {
  return this.store_name;
};


/**
 *
 * @return {!IDBKey} key id.
 */
ydn.db.Key.prototype.getId = function() {
  return this.id;
};


/**
 *
 * @return {string|number} normalized key.
 */
ydn.db.Key.prototype.getNormalizedId = function() {
  if (goog.isArray(this.id)) {
    return this.id.join(ydn.db.Key.SEP_PARENT);
  } else if (this.id instanceof Date) {
    return +(this.id);
  } else {
    return /** @type {string|number} */ (this.id);
  }
};


/**
 *
 * @return {ydn.db.Key} return parent key if it has.
 */
ydn.db.Key.prototype.getParent = function() {
  return this.parent;
};


/**
 *
 * @param {*} key key to test.
 * @return {boolean} return true if given key is a valid key for IndexedDB.
 */
ydn.db.Key.isValidKey = function(key) {
  return goog.isNumber(key) || goog.isString(key) ||
      (goog.isArray(key) && goog.array.every(/** @type {Array} */ (key),
      ydn.db.Key.isValidKey)) ||
      key instanceof Date;
};


/**
 * Clone IDBKey.
 * @param {IDBKey} key given key.
 * @return {IDBKey} key to clone.
 */
ydn.db.Key.clone = function(key) {
  if (goog.isArrayLike(key)) {
    var clone = [];
    for (var i = 0, n = key.length; i < n; i++) {
      clone[i] = key[i];
    }
    return /** @type {IDBKey} */ (clone);
  } else {
    return key;
  }
};


// Input 40
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Execute data request.
 *
 * Before invoking database request, transaction object (tx) must set
 * and active. Caller must preform setting tx. This class will not check
 * it, but run immediately. Basically thinks this as a static object.
 *
 * These classes assume requested store or index are available in the database.
 */


goog.provide('ydn.db.crud.req.RequestExecutor');
goog.require('goog.async.Deferred');
goog.require('goog.debug.Logger');
goog.require('ydn.db.InternalError');
goog.require('ydn.db.Key');



/**
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @struct
 */
ydn.db.crud.req.RequestExecutor = function(dbname, schema) {
  /**
   * @final
   * @protected
   * @type {string}
   */
  this.dbname = dbname;
  /**
   * @final
   * @protected
   * @type {!ydn.db.schema.Database}
   */
  this.schema = schema;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.crud.req.RequestExecutor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.crud.req.RequestExecuto');


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.crud.req.RequestExecutor.prototype.toString = function() {
    return 'RequestExecutor';
  };
}






// Input 41
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Interface for executing database request.
 */


goog.provide('ydn.db.crud.IOperator');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.crud.req.RequestExecutor');



/**
 * @interface
 */
ydn.db.crud.IOperator = function() {};


/**
 *
 * @param {*|!Array.<string>|string} store_name store name or names.
 * @param {(string|ydn.db.KeyRange|ydn.db.IDBKeyRange)=} opt_key_range_index
 * index name or key range.
 * @param {(ydn.db.KeyRange|ydn.db.IDBKeyRange)=} opt_key_range key range if
 * second argument is an index.
 * @param {boolean=} opt_unique count unique index key.
 * @return {!ydn.db.Request} return object in deferred function.
 */
ydn.db.crud.IOperator.prototype.count = goog.abstractMethod;


/**
 * Return object or objects of given key or keys.
 * @param {(!Object|string|!ydn.db.Key)=} opt_arg1 table name.
 * @param {(!Object|!IDBKey)=} opt_arg2
 * object key to be retrieved, if not provided,
 * all entries in the store will return.
 * @return {!ydn.db.Request} return object in deferred function.
 */
ydn.db.crud.IOperator.prototype.get = goog.abstractMethod;


/**
 * Return object or objects of given key or keys.
 * @param {(*|string|!Array.<!ydn.db.Key>)=} opt_arg1 table name.
 * @param {(string|KeyRangeJson|ydn.db.KeyRange|!Array.<!IDBKey>|number)=} opt_arg2
 * list of primary keys or key range.
 * @param {(number|KeyRangeJson|ydn.db.KeyRange)=} opt_arg3 limit.
 * @param {number=} opt_arg4 offset.
 * @param {(boolean|number)=} opt_unique name.
 * @param {boolean=} opt_arg6 reverse.
 * @param {boolean=} opt_arg7 reverse.
 * @return {!ydn.db.Request} return object in deferred function.
 */
ydn.db.crud.IOperator.prototype.values = goog.abstractMethod;


/**
 * List keys or effective keys.
 * @param {*|string} store_name or iterator.
 * @param {(string|ydn.db.KeyRange|KeyRangeJson|number)=} opt_arg1 key range
 * or index name or limit for iterator.
 * @param {(number|ydn.db.KeyRange|KeyRangeJson)=} opt_arg2 limit or key range.
 * @param {number=} opt_arg3 offset or limit.
 * @param {(boolean|number)=} opt_arg4 reverse or offset.
 * @param {boolean=} opt_arg5 reverse.
 * @param {boolean=} unique limit.
 * @return {!ydn.db.Request} result promise.
 */
ydn.db.crud.IOperator.prototype.keys = goog.abstractMethod;


/**
 * Execute ADD request either storing result to tx or callback to df.
 * @param {string|StoreSchema} store_name_or_schema store name or
 * schema.
 * @param {!Object|!Array.<!Object>} value object to put.
 * @param {*=} opt_keys out-of-line keys.
 * @return {!ydn.db.Request} return newly created keys in promise.
 */
ydn.db.crud.IOperator.prototype.add = goog.abstractMethod;


/**
 * Execute PUT request to the store of given records in delimited text.
 * @param {string} store_name table name.
 * @param {string} data delimited text to put. one object per line.
 * @param {string=} opt_delimiter field delimiter.
 */
ydn.db.crud.IOperator.prototype.load = goog.abstractMethod;


/**
 * Execute PUT request either storing result to tx or callback to df.
 * @param {string|StoreSchema|ydn.db.Key|!Array.<!ydn.db.Key>} arg1 store name
 * or schema, key or array of keys.
 * @param {(!Object|!Array.<!Object>)} value object to put.
 * @param {IDBKey|!Array.<IDBKey>=} opt_keys out-of-line keys.
 * @return {!ydn.db.Request} return newly created keys in promise.
 */
ydn.db.crud.IOperator.prototype.put = goog.abstractMethod;


/**
 * Clear a specific entry from a store or all.
 * @param {(!Array.<string>|string)=} opt_arg1 delete the table as provided
 * otherwise
 * delete all stores.
 * @param {(string|KeyRangeJson|ydn.db.KeyRange|ydn.db.IDBKeyRange)=} opt_arg2
 * delete a specific row.
 * @param {(KeyRangeJson|ydn.db.KeyRange|ydn.db.IDBKeyRange)=} opt_arg3 argument
 * control.
 * @see {@link #remove}
 * @return {!ydn.db.Request} return a deferred function.
 */
ydn.db.crud.IOperator.prototype.clear = goog.abstractMethod;


/**
 * Remove a specific entry from a store or all.
 * @param {string|ydn.db.Key|!Array.<!ydn.db.Key>} store_name store name.
 * @param {(string|number|Date|KeyRangeJson|ydn.db.KeyRange)=} opt_arg2 delete
 * a specific key or
 * key range.
 * @param {(string|number|Date|KeyRangeJson|ydn.db.KeyRange)=} opt_arg3 delete
 * a specific key or
 * key range.
 * @see {@link #remove}
 * @return {!ydn.db.Request} return number of record removed a deferred.
 */
ydn.db.crud.IOperator.prototype.remove = goog.abstractMethod;

// Input 42
/**
 * @fileoverview Interface for executing database request.
 *
 */


goog.provide('ydn.db.core.IOperator');
goog.require('ydn.db.crud.IOperator');



/**
 * @extends {ydn.db.crud.IOperator}
 * @interface
 */
ydn.db.core.IOperator = function() {};


/**
 * Map operation.
 * @param {!ydn.db.Iterator} iterator
 * @param {?function(*): (*|undefined)} callback
 * @return {!goog.async.Deferred} deferred.
 */
ydn.db.core.IOperator.prototype.map = goog.abstractMethod;


/**
 * Reduce operation.
 * @param {!ydn.db.Iterator} iterator iterator.
 * @param {function(*, *, number): *} callback callback.
 * @param {*=} opt_initial initial value.
 * @return {!goog.async.Deferred} deferred.
 */
ydn.db.core.IOperator.prototype.reduce = goog.abstractMethod;





// Input 43
/**
 * @fileoverview Execute data request.
 *
 * Before invoking database request, transaction object (tx) must set
 * and active. Caller must preform setting tx. This class will not check
 * it, but run immediately. Basically thinks this as a static object.
 */


goog.provide('ydn.db.crud.req.IRequestExecutor');
goog.provide('ydn.db.crud.req.ListType');



/**
 * @interface
 */
ydn.db.crud.req.IRequestExecutor = function() {};


/**
 * Delete given key in the object store.
 * Return number of keys deleted.
 * @param {ydn.db.Request} req request.
 * @param {string} store table name.
 * @param {(IDBKey)} id object key to be deleted.
 */
ydn.db.crud.req.IRequestExecutor.prototype.removeById = goog.abstractMethod;


/**
 * Delete given key in the object store.
 * Return number of keys deleted.
 * @param {ydn.db.Request} req request.
 * @param {(!Array.<!ydn.db.Key>)} id object key to be deleted.
 */
ydn.db.crud.req.IRequestExecutor.prototype.removeByKeys =
    goog.abstractMethod;


/**
 * Clear records in the given key range from a store.
 * Return number of keys deleted.
 * @param {ydn.db.Request} req request.
 * @param {string} store table name.
 * @param {IDBKeyRange} key range.
 */
ydn.db.crud.req.IRequestExecutor.prototype.removeByKeyRange =
    goog.abstractMethod;


/**
 * Clear records in the given key range from a store.
 * Return number of keys deleted.
 * @param {ydn.db.Request} req request.
 * @param {string} store table name.
 * @param {string} index name.
 * @param {IDBKeyRange} key range.
 */
ydn.db.crud.req.IRequestExecutor.prototype.removeByIndexKeyRange = goog.abstractMethod;


/**
 * Clear records in the given key range from a store.
 * Return number of keys deleted.
 * @param {ydn.db.Request} req request.
 * @param {string} store table name.
 * @param {IDBKeyRange} key range.
 */
ydn.db.crud.req.IRequestExecutor.prototype.clearByKeyRange =
    goog.abstractMethod;


/**
 * Clear a store or stores.
 * Return number of stores deleted.
 * @param {ydn.db.Request} req request.
 * @param {(!Array.<string>)=} store table name.
 */
ydn.db.crud.req.IRequestExecutor.prototype.clearByStores = goog.abstractMethod;


/**
 * @param {ydn.db.Request} req request.
 * @param {!Array.<string>} table store name.
 */
ydn.db.crud.req.IRequestExecutor.prototype.countStores = goog.abstractMethod;


/**
 * @param {ydn.db.Request} req request.
 * @param {string} table store name.
 * @param {IDBKeyRange} keyRange the key range.
 * @param {(string|undefined)} index name.
 * @param {boolean} unique count unique index key. Note: indexeddb cannot
 * count with unique, while websql can.
 */
ydn.db.crud.req.IRequestExecutor.prototype.countKeyRange = goog.abstractMethod;


/**
 * Return object
 * @param {ydn.db.Request} req request.
 * @param {string} store table name.
 * @param {!IDBKey} id object key to be retrieved, if not
 * provided,
 * all entries in the store will return.
 */
ydn.db.crud.req.IRequestExecutor.prototype.getById = goog.abstractMethod;


/**
 * Execute GET request callback results to df.
 * @param {ydn.db.Request} req request.
 * @param {string} store_name table name.
 * @param {!Array.<!IDBKey>} ids id to get.
 * @throws {ydn.db.InvalidKeyException}
 * @throws {ydn.error.InternalError}
 */
ydn.db.crud.req.IRequestExecutor.prototype.listByIds = goog.abstractMethod;


/**
 * Execute GET request callback results to df.
 * @param {ydn.db.Request} req request.
 * @param {!Array.<!ydn.db.Key>} keys id to get.
 */
ydn.db.crud.req.IRequestExecutor.prototype.listByKeys = goog.abstractMethod;


/**
 * Execute PUT request to the store of given records in delimited text.
 * @param {ydn.db.base.Transaction} tx
 *  @param {string} tx_no transaction number
 * @param {?function(*, boolean=)} df deferred to feed result.
 * @param {string} store_name table name.
 * @param {string} data delimited text to put. one object per line.
 * @param {string} delimiter field delimiter.
 */
ydn.db.crud.req.IRequestExecutor.prototype.putData = goog.abstractMethod;


/**
 * Put objects and return list of key inserted.
 * @param {ydn.db.Request} rq request.
 * @param {boolean} is_replace true if `put`, otherwise `add`.
 * @param {boolean} is_single true if result take only the first result.
 * @param {string} store_name store name.
 * @param {!Array.<!Object>} objs object to put.
 * @param {!Array.<IDBKey>=} opt_keys optional out-of-line keys.
 */
ydn.db.crud.req.IRequestExecutor.prototype.insertObjects = goog.abstractMethod;


/**
 * @param {ydn.db.Request} req request.
 * @param {!Array.<Object>} objs object to put.
 * @param {!Array.<!ydn.db.Key>} keys list of keys.
 */
ydn.db.crud.req.IRequestExecutor.prototype.putByKeys = goog.abstractMethod;


/**
 * Execute GET request callback results to df.
 * @param {ydn.db.Request} req request.
 * @param {ydn.db.base.QueryMethod} type result type.
 * @param {string} store name.
 * @param {string?} index name.
 * @param {IDBKeyRange} key range to get.
 * @param {number} limit the results.
 * @param {number} offset skip first results.
 * @param {boolean} reverse to sort reverse order.
 * @param {boolean} unique unique key.
 * @param {Array.<IDBKey|undefined>=} opt_position last cursor position.
 */
ydn.db.crud.req.IRequestExecutor.prototype.list =
    goog.abstractMethod;


// Input 44
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Interface for index base request.
 *
 */


goog.provide('ydn.db.core.req.IRequestExecutor');
goog.require('ydn.db.Streamer');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.db.crud.req.IRequestExecutor');



/**
 * @interface
 * @extends {ydn.db.crud.req.IRequestExecutor}
 */
ydn.db.core.req.IRequestExecutor = function() {};


/**
 * @param {ydn.db.base.Transaction} tx
 * @param {string} tx_no tx request label.
 * @param {string} store_name schema.
 * @param {ydn.db.base.QueryMethod=} query query method.
 * @return {!ydn.db.core.req.AbstractCursor} cursor.
 */
ydn.db.core.req.IRequestExecutor.prototype.getCursor = goog.abstractMethod;


/**
 * @param {ydn.db.base.Transaction} tx
 * @param {string} tx_no transaction number.
 * @param {string} store_name
 * @param {string=} opt_index_name
 * @return {!ydn.db.Streamer}
 */
ydn.db.core.req.IRequestExecutor.prototype.getStreamer = goog.abstractMethod;

// Input 45
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Custom deferred class for transaction facilitating
 * synchronization logic and aborting transaction.
 *
 * Before this implementation, abort method was dynamically attached to
 * database instance. That approach is limited to aborting during request
 * promise callbacks handler. Also no way to check the request can be aborted
 * or not. With this implementation abort method is attached to request, i.e.,
 * to this deferred object supporting enqueriable abort.
 *
 * Before this implementation, synchronization logic uses two or more deferred
 * objects, now sync logic facilities are built-in.
 *
 * Rationale for using custom deferred class.
 * ------------------------------------------
 * In general coding pattern, usage of custom class is discouraged if
 * composition of existing classes is application. Here, this custom deferred
 * class can, in face, be composed using goog.async.Deferred and/or
 * goog.events.EventTarget. However, high frequency usage of this class is
 * an optimization is desirable. If goog.async.Deferred were used,
 * #await will require at least two goog.async.Deferred objects for each
 * transformer. Also note that #awaitDeferred is different from #wait.
 * Furthermore, handling tx and logging with custom label will be messy with
 * Deferred.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.Request');
goog.provide('ydn.db.Request.Method');
goog.require('ydn.async.Deferred');
goog.require('goog.debug.Logger');
goog.require('ydn.db.base.Transaction');



/**
 * A Deferred with progress event.
 *
 * @param {ydn.db.Request.Method} method request method.
 * @param {Function=} opt_onCancelFunction A function that will be called if the
 *     Deferred is canceled. If provided, this function runs before the
 *     Deferred is fired with a {@code CanceledError}.
 * @param {Object=} opt_defaultScope The default object context to call
 *     callbacks and errbacks in.
 * @constructor
 * @extends {ydn.async.Deferred}
 * @struct
 */
ydn.db.Request = function(method, opt_onCancelFunction, opt_defaultScope) {
  goog.base(this, opt_onCancelFunction, opt_defaultScope);
  this.method_ = method;
  /**
   * progress listener callbacks.
   * @type {!Array.<!Array>}
   * @private
   */
  this.progbacks_ = [];
  /**
   * transaction ready listener callbacks.
   * @type {!Array.<!Array>}
   * @private
   */
  this.txbacks_ = [];
  /**
   * request branches.
   * @type {!Array.<!Array>}
   * @private
   */
  this.transformers_ = [];
  this.tx_ = null;
  this.tx_label_ = '';
  this.copy_count_ = 0;
};
goog.inherits(ydn.db.Request, ydn.async.Deferred);


/**
 * @define {boolean} debug flag.
 */
ydn.db.Request.DEBUG = false;


/**
 * @type {ydn.db.Request.Method} method request method.
 * @private
 */
ydn.db.Request.prototype.method_;


/**
 * @type {ydn.db.base.Transaction} transaction object.
 */
ydn.db.Request.prototype.tx_;


/**
 * @type {string} transaction label.
 * @private
 */
ydn.db.Request.prototype.tx_label_ = '';


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.Request.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.Request');


/**
 * Set active transaction. This will invoke tx listener callbacks.
 * @param {ydn.db.base.Transaction} tx active transaction.
 * @param {string} label tx label.
 * @final
 */
ydn.db.Request.prototype.setTx = function(tx, label) {
  goog.asserts.assert(!this.tx_, 'TX already set.');
  this.tx_ = tx;
  this.tx_label_ = label;
  this.logger.finer(this + ' BEGIN');
  if (tx) {
    for (var i = 0; i < this.txbacks_.length; i++) {
      var tx_callback = this.txbacks_[i][0];
      var scope = this.txbacks_[i][1];
      tx_callback.call(scope, tx);
    }
    this.txbacks_.length = 0;
  }
  this.logger.finer(this + ' END');
};


/**
 * @return {!ydn.db.Request} active tx copy of this request.
 */
ydn.db.Request.prototype.copy = function() {
  // goog.asserts.assert(this.tx_, 'only active request can be copied');
  var rq = new ydn.db.Request(this.method_);
  this.copy_count_++;
  rq.setTx(this.tx_, this.tx_label_ + 'C' + this.copy_count_);
  return rq;
};


/**
 * Remove tx when tx is inactive.
 */
ydn.db.Request.prototype.removeTx = function() {
  this.tx_ = null;
};


/**
 * @return {ydn.db.base.Transaction}
 * @final
 */
ydn.db.Request.prototype.getTx = function() {
  return this.tx_;
};


/**
 * @return {ydn.db.Request.Method}
 */
ydn.db.Request.prototype.getMethod = function() {
  return this.method_;
};


/**
 * @return {boolean}
 * @final
 */
ydn.db.Request.prototype.canAbort = function() {
  return !!this.tx_;
};


/**
 * Abort active transaction.
 * @see #canAbort
 * @final
 */
ydn.db.Request.prototype.abort = function() {
  if (this.tx_) {
    this.logger.finer(this + ' aborting');
    if (goog.isFunction(this.tx_.abort)) {
      this.tx_.abort();
    } else if (goog.isFunction(this.tx_.executeSql)) {
      /**
       * @param {SQLTransaction} transaction transaction.
       * @param {SQLResultSet} results results.
       */
      var callback = function(transaction, results) {

      };
      /**
       * @param {SQLTransaction} tr transaction.
       * @param {SQLError} error error.
       * @return {boolean} true to roll back.
       */
      var error_callback = function(tr, error) {
        // console.log(error);
        return true; // roll back
      };
      this.tx_.executeSql('ABORT', [], callback, error_callback);
      // this will cause error on SQLTransaction and WebStorage.
      // the error is wanted because there is no way to abort a transaction in
      // WebSql. It is somehow recommanded workaround to abort a transaction.
    } else {
      throw new ydn.debug.error.NotSupportedException();
    }
  } else {
    var msg = goog.DEBUG ? 'No active transaction' : '';
    throw new ydn.db.InvalidStateError(msg);
  }
};


/**
 * Resolve a database request. This will trigger invoking
 * awaiting transformer callback function sequencially and asynchorniously
 * and finally invoke Deferred.callback method to fulfill the promise.
 * @param {*} value result from database request.
 * @param {boolean=} opt_failed true if request fail.
 * @final
 */
ydn.db.Request.prototype.setDbValue = function(value, opt_failed) {
  var tr = this.transformers_.shift();
  var failed = !!opt_failed;
  if (tr) {
    var me = this;
    var fn = tr[0];
    var scope = tr[1];
    fn.call(scope, value, failed, function(tx_value, f2) {
      me.setDbValue(tx_value, f2);
    });
  } else {
    if (ydn.db.Request.DEBUG) {
      window.console.log(this + ' receiving ' + (failed ? 'fail' : 'value'),
          value);
    }
    if (failed) {
      this.errback(value);
    } else {
      this.callback(value);
    }
  }
};


/**
 * Add db value transformer. Transformers are invoked successively.
 * @param {function(this: T, *, boolean, function(*, boolean=))} tr a
 * transformer.
 * @param {T=} opt_scope An optional scope to call the await in.
 * @template T
 * @see {goog.async.Deferred#awaitDeferred}
 */
ydn.db.Request.prototype.await = function(tr, opt_scope) {
  goog.asserts.assert(!this.hasFired(), 'transformer cannot be added after' +
      ' resolved.');
  this.transformers_.push([tr, opt_scope]);
};


/**
 * Register a callback function to be called when tx ready.
 * @param {!function(this:T,?):?} fun The function to be called on progress.
 * @param {T=} opt_scope An optional scope to call the progback in.
 * @return {!goog.async.Deferred} This Deferred.
 * @template T
 */
ydn.db.Request.prototype.addTxback = function(fun, opt_scope) {
  if (this.tx_) {
    fun.call(opt_scope, this.tx_);
  } else {
    this.txbacks_.push([fun, opt_scope]);
  }
  return this;
};


/**
 * @inheritDoc
 */
ydn.db.Request.prototype.callback = function(opt_result) {
  this.logger.finer(this + ' SUCCESS');
  goog.base(this, 'callback', opt_result);
  this.dispose_();
};


/**
 * @inheritDoc
 */
ydn.db.Request.prototype.errback = function(opt_result) {
  this.logger.finer(this + ' ERROR');
  goog.base(this, 'errback', opt_result);
  this.dispose_();
};


/**
 * Determine the current state of a Deferred object.
 * Note: This is to satisfy JQuery build export. Closure project should use
 * @see #hasFired instead.
 * @return {string}
 * @suppress {accessControls}
 */
ydn.db.Request.prototype.state = function() {
  if (this.hasFired()) {
    if (this.hadError_) {
      return 'rejected';
    } else {
      return 'resolved';
    }
  } else {
    return 'pending';
  }
};


/**
 * Release references.
 * @private
 */
ydn.db.Request.prototype.dispose_ = function() {
  this.tx_ = null;
  this.tx_label_ = this.tx_label_;
};


/**
 * Request label.
 * @return {string} request label.
 */
ydn.db.Request.prototype.getLabel = function() {
  var label = '';
  if (this.tx_label_) {
    label = this.tx_ ? '*' : '';
    label = '[' + this.tx_label_ + label + ']';
  }
  return this.method_ + label;
};


/**
 * @param {ydn.db.Request.Method} method method.
 * @param {*} value success value.
 * @return {!ydn.db.Request} request.
 */
ydn.db.Request.succeed = function(method, value) {
  var req = new ydn.db.Request(method);
  req.setDbValue(value);
  return req;
};


/**
 * Return a Deferred's Promise object, as required by jQuery.
 * @return {!goog.async.Deferred}
 */
ydn.db.Request.prototype.promise = function() {
  // Ref: https://github.com/jquery/jquery/blob/
  // cb37994d76afb45efc3b606546349ed4e695c053/src/deferred.js#L34
  // Note: promise function return an object having `done`, `fail` and
  // `progress` functions. Since a request object satisfy the requirement, this
  // simply return itself.
  return this;
};


/**
 * @inheritDoc
 */
ydn.db.Request.prototype.toString = function() {
  return 'Request:' + this.getLabel();
};


/**
 * Exhausts the execution sequence while a result is available. The result may
 * be modified by callbacks or errbacks, and execution will block if the
 * returned result is an incomplete Deferred.
 *
 * @override Remove try/catch block for performance (and better debugging)
 * @suppress {accessControls}
 */
ydn.db.Request.prototype.fire_ = function() {
  if (this.unhandledExceptionTimeoutId_ && this.hasFired() &&
      this.hasErrback_()) {
    // It is possible to add errbacks after the Deferred has fired. If a new
    // errback is added immediately after the Deferred encountered an unhandled
    // error, but before that error is rethrown, cancel the rethrow.
    goog.global.clearTimeout(this.unhandledExceptionTimeoutId_);
    delete this.unhandledExceptionTimeoutId_;
  }

  if (this.parent_) {
    this.parent_.branches_--;
    delete this.parent_;
  }

  var res = this.result_;
  var unhandledException = false;
  var isNewlyBlocked = false;

  while (this.sequence_.length && !this.blocked_) {
    var sequenceEntry = this.sequence_.shift();

    var callback = sequenceEntry[0];
    var errback = sequenceEntry[1];
    var scope = sequenceEntry[2];

    var f = this.hadError_ ? errback : callback;
    if (f) {
      /* preserveTry */
      // try {
      var ret = f.call(scope || this.defaultScope_, res);

      // If no result, then use previous result.
      if (goog.isDef(ret)) {
        // Bubble up the error as long as the return value hasn't changed.
        this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
        this.result_ = res = ret;
      }

      if (res instanceof goog.async.Deferred) {
        isNewlyBlocked = true;
        this.blocked_ = true;
      }

      /* } catch (ex) {
       res = ex;
       this.hadError_ = true;

       if (!this.hasErrback_()) {
       // If an error is thrown with no additional errbacks in the queue,
       // prepare to rethrow the error.
       unhandledException = true;
       }
       } */
    }
  }

  this.result_ = res;

  if (isNewlyBlocked) {
    res.addCallbacks(
        goog.bind(this.continue_, this, true /* isSuccess */),
        goog.bind(this.continue_, this, false /* isSuccess */));
    res.blocking_ = true;
  } else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
      !(res instanceof goog.async.Deferred.CanceledError)) {
    this.hadError_ = true;
    unhandledException = true;
  }

  if (unhandledException) {
    // Rethrow the unhandled error after a timeout. Execution will continue, but
    // the error will be seen by global handlers and the user. The throw will
    // be canceled if another errback is appended before the timeout executes.
    // The error's original stack trace is preserved where available.
    this.unhandledExceptionTimeoutId_ = goog.global.setTimeout(
        goog.functions.fail(res), 0);
  }
};


/**
 * @inheritDoc
 */
ydn.db.Request.prototype.toJSON = function() {
  var label = this.tx_label_ || '';
  var m = label.match(/B(\d+)T(\d+)(?:Q(\d+?))?(?:R(\d+))?/) || [];
  return {
    'method': this.method_ ? this.method_.split(':') : [],
    'branchNo': parseFloat(m[1]),
    'transactionNo': parseFloat(m[2]),
    'queueNo': parseFloat(m[3]),
    'requestNo': parseFloat(m[4])
  };
};


/**
 * Request method.
 * @enum {string}
 */
ydn.db.Request.Method = {
  ADD: goog.DEBUG ? 'add' : 'a',
  ADDS: goog.DEBUG ? 'add:array' : 'b',
  CLEAR: goog.DEBUG ? 'clear' : 'c',
  COUNT: goog.DEBUG ? 'count' : 'd',
  GET: goog.DEBUG ? 'get' : 'e',
  GET_BY_KEY: goog.DEBUG ? 'get:key' : 'ek',
  GET_ITER: goog.DEBUG ? 'get:iter' : 'f',
  KEYS: goog.DEBUG ? 'keys' : 'g',
  KEYS_ITER: goog.DEBUG ? 'keys:iter' : 'h',
  KEYS_INDEX: goog.DEBUG ? 'keys:iter:index' : 'i',
  LIST: goog.DEBUG ? 'list' : 'i2',
  LOAD: goog.DEBUG ? 'load' : 'i3',
  MAP: goog.DEBUG ? 'map' : 'i4',
  OPEN: goog.DEBUG ? 'open' : 'i5',
  PUT: goog.DEBUG ? 'put' : 'j',
  PUTS: goog.DEBUG ? 'put:array' : 'k',
  PUT_KEYS: goog.DEBUG ? 'put:keys' : 'l',
  REDUCE: goog.DEBUG ? 'reduce' : 'm0',
  REMOVE_ID: goog.DEBUG ? 'rm' : 'm',
  REMOVE: goog.DEBUG ? 'rm:iter' : 'n',
  REMOVE_KEYS: goog.DEBUG ? 'rm:keys' : 'o',
  REMOVE_INDEX: goog.DEBUG ? 'rm:iter:index' : 'p',
  RUN: goog.DEBUG ? 'run' : 'q',
  SCAN: goog.DEBUG ? 'scan' : 'qa',
  SEARCH: goog.DEBUG ? 'search' : 'qb',
  SQL: goog.DEBUG ? 'sql' : 'r',
  VALUES: goog.DEBUG ? 'values' : 's',
  VALUES_ITER: goog.DEBUG ? 'values:iter' : 't',
  VALUES_INDEX: goog.DEBUG ? 'values:iter:index' : 'u',
  VALUES_IDS: goog.DEBUG ? 'values:array' : 'v',
  VALUES_KEYS: goog.DEBUG ? 'values:keys' : 'w',
  VERSION_CHANGE: goog.DEBUG ? 'IDBVersionChangeEvent ' : 'vc',
  NONE: ''
};

// Input 46
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Transaction thread.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.tr.Thread');
goog.provide('ydn.db.tr.Thread.Policy');
goog.require('ydn.db.Request');
goog.require('goog.debug.Logger');




/**
 * Create transaction queue providing methods to run in non-overlapping
 * transactions.
 *
 * @param {!ydn.db.tr.Storage} storage base storage.
 * @param {number} ptx_no transaction queue number.
 * @param {ydn.db.tr.Thread.Policy=} opt_policy
 * @param {!Array.<string>=} opt_store_names store names as scope.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode as scope.
 * @param {number=} opt_max_tx_no limit number of transaction created.
 * @constructor
 * @struct
 */
ydn.db.tr.Thread = function(storage, ptx_no, opt_policy,
                            opt_store_names, opt_mode, opt_max_tx_no) {

  /**
   * @final
   * @type {!ydn.db.tr.Storage}
   * @private
   */
  this.storage_ = storage;

  /**
   * Transaction thread number.
   * @final
   */
  this.q_no_ = ptx_no;

  /**
   * Transaction number, increase one as a transaction created from this thread.
   * @type {number}
   */
  this.tx_no_ = 0;

  /**
   * Request number, increase one as a request created from this thread. Reset
   * to 0 on each transaction.
   * @type {number}
   */
  this.r_no_ = 0;


  /**
   * @final
   * @protected
   */
  this.scope_store_names = opt_store_names;

  /**
   * @final
   * @protected
   */
  this.scope_mode = opt_mode;

  /**
   * @final
   * @protected
   */
  this.policy = opt_policy || ydn.db.tr.Thread.Policy.SINGLE;

  /**
   * @final
   * @protected
   */
  this.max_tx_no = opt_max_tx_no || 0;

  /**
   * Generator function on spawn synchronous thread.
   * @type {Function}
   * @private
   */
  this.generator_;

  /**
   * Generator completed callback.
   * @type {ydn.db.Request}
   * @private
   */
  this.gen_req_ = null;

  /**
   * Set break the active transaction by commit method.
   * @type {boolean}
   * @private
   */
  this.break_tx_ = false;
  /**
   * @type {number}
   * @private
   */
  this.yield_no_ = 0;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.Thread.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.Thread');


/**
 * Set generator
 * @param {Function} gen
 * @return {Function} callback to invoke when tx is committed or aborted.
 */
ydn.db.tr.Thread.prototype.setGenerator = function(gen, req) {
  goog.asserts.assert(!this.generator_, 'thread can have only one generator');
  this.generator_ = gen;
  this.gen_req_ = req;
  return goog.bind(this.onGenTxCommitted_, this);
};


/**
 * @param {ydn.db.base.TxEventTypes} type
 * @param {*} e
 * @private
 */
ydn.db.tr.Thread.prototype.onGenTxCommitted_ = function(type, e) {
  var done = type == ydn.db.base.TxEventTypes.COMPLETE ||
      type == ydn.db.base.TxEventTypes.ABORT;
  if (done) {
    if (this.break_tx_) {
      this.logger.finest('tx ' + this.getTxNo() + ' committed');
      this.break_tx_ = false;
    } else {
      this.logger.finest('Generator done ' + this.break_tx_);
      var success = type == ydn.db.base.TxEventTypes.COMPLETE;
      this.gen_req_.setDbValue(this.getTxNo(), !success);
    }

  }
};


/**
 * Commit active transaction by setTimeout.
 */
ydn.db.tr.Thread.prototype.commit = function() {
  if (!goog.isDef(this.generator_)) {
    throw new ydn.debug.error.InvalidOperationException('Transaction thread' +
        ' not running in a generator');
  } else if (this.generator_) {
    this.logger.finest('Receive to commit tx ' + this.getTxNo() + ' on yield ' +
        this.yield_no_);
    this.break_tx_ = true;
  } else {
    // null
    throw new ydn.debug.error.InvalidOperationException('Coroutine' +
        ' transaction thread has been ended');
  }
};


/**
 * Send next result value to generator.
 * @param x
 * @private
 */
ydn.db.tr.Thread.prototype.sendNext_ = function(x) {
  if (this.break_tx_) {
    this.logger.finest('waiting tx to be committed for yielding ' +
        this.yield_no_);
    var me = this;
    // let transaction be committed
    setTimeout(function() {
      // and create a new transaction.
      me.processTx(function(tx) {
        // new tx is active, start next request.
        // me.gen_req_.setTx(tx); // todo set correct tx, current implementation
        // not allow setting multiple tx setting.
        me.logger.finest('sending result to yield ' + me.yield_no_);
        me.generator_['next'](x);
      }, /** @type {!Array.<string>} */ (me.scope_store_names), me.scope_mode,
          goog.bind(me.onGenTxCommitted_, me));

    }, 4);
  } else {
    this.logger.finest('sending result to yield ' + this.yield_no_);
    this.generator_['next'](x);
  }
  this.yield_no_++;
};


/**
 * Create an request.
 * @param {ydn.db.Request.Method} method request method.
 * @param {!Array.<string>} store_names store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_oncompleted handler.
 * @return {!ydn.db.Request}
 */
ydn.db.tr.Thread.prototype.request = function(method, store_names, opt_mode,
                                              opt_oncompleted) {
  var req = new ydn.db.Request(method);
  if (this.generator_) {
    req.addCallbacks(function(x) {
      this.sendNext_(x);
    }, function(e) {
      var err = e;
      if (!(e instanceof Error)) {
        err = new Error();
        err['error'] = e;
      }
      this.sendNext_(err);
    }, this);
  }

  return req;
};


/**
 * @param {!goog.async.Deferred} df deferred object to intersect the request.
 * @param {?function((ydn.db.base.Transaction),
 * string, ?function(*, boolean=))} callback
 *   callback when executor is ready.
 * @param {!Array.<string>} store_names store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode} mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_oncompleted handler.
 */
ydn.db.tr.Thread.prototype.exec = goog.abstractMethod;


/**
 * Abort an active transaction.
 */
ydn.db.tr.Thread.prototype.abort = goog.abstractMethod;


/**
 *
 * @return {number} transaction count.
 */
ydn.db.tr.Thread.prototype.getTxNo = function() {
  return this.tx_no_;
};


/**
 *
 * @return {string|undefined} mechansim type.
 */
ydn.db.tr.Thread.prototype.type = function() {
  return this.storage_.getType();
};



/**
 *
 * @return {number} transaction queue number.
 */
ydn.db.tr.Thread.prototype.getQueueNo = function() {
  return this.q_no_;
};


/**
 * Add or update a store issuing a version change event.
 * @protected
 * @param {!StoreSchema|!ydn.db.schema.Store} store schema.
 * @return {!goog.async.Deferred} promise.
 */
ydn.db.tr.Thread.prototype.addStoreSchema = function(store) {
  return this.storage_.addStoreSchema(store);
};


/**
 * @protected
 * @return {!ydn.db.tr.Storage} storage.
 */
ydn.db.tr.Thread.prototype.getStorage = function() {
  return this.storage_;
};


/**
 *
 * @return {string} label.
 */
ydn.db.tr.Thread.prototype.getLabel = function() {
  return 'B' + this.q_no_ + 'T' + this.tx_no_;
};


/**
 * Create a new isolated transaction. After creating a transaction, use
 * {@link #getTx} to received an active transaction. If transaction is not
 * active, it return null. In this case a new transaction must re-create.
 * @param {Function} trFn function that invoke in the transaction.
 * @param {!Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_oncompleted handler.
 */
ydn.db.tr.Thread.prototype.processTx = goog.abstractMethod;


/**
 * Request type.
 * @enum {string}
 */
ydn.db.tr.Thread.Policy = {
  MULTI: 'multi',
  REPEAT: 'repeat',
  ALL: 'all',
  ATOMIC: 'atomic',
  SINGLE: 'single'
};


/**
 * Abort an active transaction.
 * @param {ydn.db.base.Transaction} tx transaction to be aborted.
 */
ydn.db.tr.Thread.abort = function(tx) {
  if (tx) {
    if (goog.isFunction(tx.abort)) {
      tx.abort();
    } else if (goog.isFunction(tx.executeSql)) {
      /**
       * @param {SQLTransaction} tr transaction.
       * @param {SQLError} error error.
       * @return {boolean} true to roll back.
       */
      var error_callback = function(tr, error) {
        // console.log(error);
        return true; // roll back
      };
      tx.executeSql('ABORT', [], null, error_callback);
      // this will cause error on SQLTransaction and WebStorage.
      // the error is wanted because there is no way to abort a transaction in
      // WebSql. It is somehow recommanded workaround to abort a transaction.
    } else {
      throw new ydn.debug.error.NotSupportedException();
    }

  } else {
    throw new ydn.db.InvalidStateError('No active transaction');
  }
};

// Input 47
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Transaction mutex.
 *
 * Record transaction mutex and execution status.
 */

goog.provide('ydn.db.tr.Mutex');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ydn.db.InvalidStateError');



/**
 * Create transaction mutex.
 * @param {number} tr_no track number.
 * @constructor
 * @const
 */
ydn.db.tr.Mutex = function(tr_no) {
  this.tr_no = tr_no;
  this.tx_ = null;
  /**
   * Transaction counter.
   * @type {number}
   * @private
   */
  this.tx_count_ = 0;
};


/**
 * @type {ydn.db.base.Transaction}
 * @private
 */
ydn.db.tr.Mutex.prototype.tx_;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.Mutex.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.Mutex');


/**
 * @const
 * @type {boolean}
 */
ydn.db.tr.Mutex.DEBUG = false;


/**
 * Newly created transaction it push to mutex and lock.
 * @final
 * @param {ydn.db.base.Transaction} tx the transaction object.
 * @param {Array.<string>} store_names scope store name.
 * @param {ydn.db.base.TransactionMode} mode tx mode.
 */
ydn.db.tr.Mutex.prototype.up = function(tx, store_names, mode) {

  if (ydn.db.tr.Mutex.DEBUG) {
    window.console.log(this + ': up');
  }
  // In compiled code, it is permissible to overlap transaction,
  // rather than cause error.
  goog.asserts.assert(!this.tx_, this + ': transaction overlap');

  this.tx_ = tx;

  this.is_locked_ = false;

  /**
   *
   * @type {boolean}
   * @private
   */
  this.out_of_scope_ = false;

  this.store_names = store_names;

  this.mode = mode;

  this.tx_count_++;

  this.oncompleted = null;

};


/**
 * @protected
 * @type {Array.<string>}
 */
ydn.db.tr.Mutex.prototype.store_names = null;


/**
 * @protected
 * @type {?ydn.db.base.TransactionMode}
 */
ydn.db.tr.Mutex.prototype.mode;


/**
 *
 * @param {Array.<string>} store_names
 * @param {ydn.db.base.TransactionMode?} mode
 * @return {boolean}
 */
ydn.db.tr.Mutex.prototype.sameScope = function(store_names, mode) {
  if (!this.store_names || !this.mode) {
    return false;
  }
  if (mode != this.mode) {
    return false;
  }
  if (this.store_names.length != store_names.length) {
    return false;
  }
  for (var i = 0; i < store_names.length; i++) {
    if (this.store_names.indexOf(store_names[i]) == -1) {
      return false;
    }
  }
  return true;
};


/**
 *
 * @param {Array.<string>} store_names
 * @param {ydn.db.base.TransactionMode} mode
 * @return {boolean}
 */
ydn.db.tr.Mutex.prototype.subScope = function(store_names, mode) {
  if (!this.store_names || !this.mode) {
    return false;
  }
  if (mode != this.mode) {
    if (this.mode != ydn.db.base.TransactionMode.READ_WRITE ||
        mode != ydn.db.base.TransactionMode.READ_ONLY) {
      return false;
    }
  }
  if (store_names.length > this.store_names.length) {
    return false;
  }
  for (var i = 0; i < store_names.length; i++) {
    if (this.store_names.indexOf(store_names[i]) == -1) {
      return false;
    }
  }
  return true;
};


/**
 * Transaction is released and mutex is unlock.
 * @final
 * @param {ydn.db.base.TxEventTypes} type event type.
 * @param {*} event event.
 */
ydn.db.tr.Mutex.prototype.down = function(type, event) {

  //goog.asserts.assertObject(this.tx_, 'mutex already unlocked');
  if (this.tx_) {

    if (ydn.db.tr.Mutex.DEBUG) {
      window.console.log(this + ': down');
    }
    // down must be call only once by those who up
    this.tx_ = null;
    this.store_names = null;
    this.mode = null;

    if (goog.isFunction(this.oncompleted)) {
      this.oncompleted(type, event);
    }
    this.oncompleted = null;
  } else {
    this.logger.warning(this + ' has no TX to be unlocked for ' + type);
  }

};


/**
 * Transaction callback function is out of scope. We no longer accepting adding
 * listeners.
 */
ydn.db.tr.Mutex.prototype.out = function() {
  this.out_of_scope_ = true;
  // interestingly tx_ can still be use even after out of scope from the
  // transaction callback. This is the whole reason we are
  // having this class. Otherwise, transaction scope handling
  // will be very simple.
};


/**
 * True if call while in transaction callback scope. Transaction callback
 * is out of scope when a request is returning a result on success or error
 * callback.
 * @return {boolean} return true if call while in transaction callback scope.
 */
ydn.db.tr.Mutex.prototype.inScope = function() {
  return !this.out_of_scope_;
};


/**
 * Transaction is explicitly set not to do next transaction.
 */
ydn.db.tr.Mutex.prototype.lock = function() {
  if (ydn.db.tr.Mutex.DEBUG) {
    window.console.log(this + ': locked');
  }
  this.is_locked_ = true;
};


/**
 * Get number of transaction count.
 * @final
 * @return {number} transaction count.
 */
ydn.db.tr.Mutex.prototype.getTxCount = function() {
  return this.tx_count_;
};


/**
 *
 * @return {boolean} get done flag.
 */
ydn.db.tr.Mutex.prototype.isLocked = function() {
  return this.is_locked_;
};


/**
 * Transaction object is active.
 * @final
 * @return {boolean} true if transaction is active.
 */
ydn.db.tr.Mutex.prototype.isActive = function() {
  return !!this.tx_;
};


/**
 * Transaction object is available.
 * @final
 * @return {boolean} true if the transaction is available.
 */
ydn.db.tr.Mutex.prototype.isAvailable = function() {
  return !this.is_locked_;
};


/**
 * Transaction object is active and not done.
 * @final
 * @return {boolean} true if transaction is active and available.
 */
ydn.db.tr.Mutex.prototype.isActiveAndAvailable = function() {
  return this.isActive() && this.isAvailable();
};


/**
 * Add a transaction complete (also error and abort) event. The listener will
 * be invoked after receiving one of these three events and before executing
 * next transaction. However, it is recommended that listener is not used
 * for transaction logistic tracking, which should, in fact, be tracked request
 * level. Use this listener to release resource for robustness. Any error on
 * the listener will be swallowed.
 * @type {?function(string=, *=)} fn first argument is either 'complete',
 * 'error', or 'abort' and second argument is event.
 */
ydn.db.tr.Mutex.prototype.oncompleted = null;


/**
 * Return current active transaction if available. Transaction consumer must
 * check {@link #isActiveAndAvailable} if this transaction object
 * should be used.
 * @return {ydn.db.base.Transaction} transaction
 * object.
 */
ydn.db.tr.Mutex.prototype.getTx = function() {
  return this.tx_;
};


/**
 *
 * @return {string} label.
 */
ydn.db.tr.Mutex.prototype.getLabel = function() {
  return 'B' + this.tr_no + 'T' + this.tx_count_;
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.Mutex.prototype.toString = function() {
    var s = !!this.tx_ ? '*' : '';
    return 'Mutex:' + this.getLabel() + s;
  };
}



// Input 48
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Transaction queue.
 *
 * A transaction is used to crate non-overlapping transaction so that each
 * database methods are atomic and run in order.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.tr.Serial');
goog.require('ydn.db.tr.Thread');
goog.require('ydn.db.tr.Mutex');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Create transaction queue providing methods to run in non-overlapping
 * transactions.
 *
 * @param {!ydn.db.tr.Storage} storage base storage.
 * @param {number} ptx_no transaction queue number.
 * @param {ydn.db.tr.Thread.Policy=} opt_policy transaction policy.
 * @param {!Array.<string>=} opt_store_names store names as scope.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode as scope.
 * @param {number=} opt_max_tx_no limit number of transaction created.
 * @constructor
 * @extends {ydn.db.tr.Thread}
 * @struct
 */
ydn.db.tr.Serial = function(storage, ptx_no, opt_policy,
                            opt_store_names, opt_mode, opt_max_tx_no) {

  goog.base(this, storage, ptx_no, opt_policy,
      opt_store_names, opt_mode, opt_max_tx_no);

  /**
   * @type {!Array.<{
   *    fnc: Function,
   *    scope: string,
   *    store_names: !Array.<string>,
   *    mode: ydn.db.base.TransactionMode,
   *    oncompleted: function (ydn.db.base.TxEventTypes.<string>, *)
   * }>}
   * @final
   * @private
   */
  this.trQueue_ = [];

  /**
   * @final
   * @type {Array.<Function>}
   * @private
   */
  this.completed_handlers_ = [];

  /**
   * Transaction object is sed when receiving a request before result df
   * callback and set null after that callback so that it can be aborted
   * in the callback.
   * In general, this tx may be different from running tx.
   * @type {ydn.db.base.Transaction}
   * @protected
   */
  this.s_request_tx = null;

  /**
   * One database can have only one transaction.
   * @type {!ydn.db.tr.Mutex}
   * @private
   * @final
   */
  this.mu_tx_ = new ydn.db.tr.Mutex(ptx_no);

  /**
   * @final
   * @private
   */
  this.max_tx_no_ = opt_max_tx_no || 0;
  /**
   * A flag to indicate a transaction has been placed to the storage mechanism.
   * @type {boolean}
   * @private
   */
  this.has_tx_started_ = false;
};
goog.inherits(ydn.db.tr.Serial, ydn.db.tr.Thread);


/**
 * @const
 * @type {boolean}
 */
ydn.db.tr.Serial.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.Serial.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.Serial');


/**
 * @protected
 * @return {ydn.db.tr.Mutex} mutex.
 */
ydn.db.tr.Serial.prototype.getMuTx = function() {
  return this.mu_tx_;
};


/**
 * Obtain active consumable transaction object.
 * @return {ydn.db.tr.Mutex} transaction object if active and available.
 */
ydn.db.tr.Serial.prototype.getActiveTx = function() {
  return this.mu_tx_.isActiveAndAvailable() ? this.mu_tx_ : null;
};


/**
 *
 * @return {boolean} true if trnasaction is active and available.
 */
ydn.db.tr.Serial.prototype.isActive = function() {
  return this.mu_tx_.isActiveAndAvailable();
};


/**
 * @param {!Array.<string>} store_names store names for scope.
 * @param {ydn.db.base.TransactionMode} mode tx mode.
 * @return {boolean} return true if given scope and mode is compatible with
 * active transaction and should be reuse.
 * @protected
 */
ydn.db.tr.Serial.prototype.reusedTx = function(store_names, mode) {
  if (this.policy == ydn.db.tr.Thread.Policy.MULTI) {
    return this.mu_tx_.subScope(store_names, mode);
  } else if (this.policy == ydn.db.tr.Thread.Policy.REPEAT) {
    return this.mu_tx_.sameScope(store_names, mode);
  } else if (this.policy == ydn.db.tr.Thread.Policy.ALL) {
    return true;
  } else {
    return false; // SINGLE and ATOMIC
  }
};


/**
 * @return {ydn.db.base.Transaction} active transaction object.
 */
ydn.db.tr.Serial.prototype.getTx = function() {
  return this.mu_tx_.isActiveAndAvailable() ? this.mu_tx_.getTx() : null;
};


/**
 * Transaction is explicitly set not to do next transaction.
 */
ydn.db.tr.Serial.prototype.lock = function() {
  this.mu_tx_.lock();
};


/**
 * @const
 * @type {number} maximun number of transaction queue.
 */
ydn.db.tr.Serial.MAX_QUEUE = 1000;


/**
 * Run the first transaction task in the queue. DB must be ready to do the
 * transaction.
 * @private
 */
ydn.db.tr.Serial.prototype.popTxQueue_ = function() {

  var task = this.trQueue_.shift();
  if (task) {
    if (ydn.db.tr.Serial.DEBUG) {
      this.logger.finest('pop tx queue[' + this.trQueue_.length + ']');
    }
    this.processTx(task.fnc, task.store_names, task.mode, task.oncompleted);
  }
  //this.last_queue_checkin_ = goog.now();
};


/**
 *
 * @return {Array.<string>}
 */
ydn.db.tr.Serial.prototype.peekScopes = function() {
  if (this.trQueue_.length > 0) {
    return this.trQueue_[0].store_names;
  } else {
    return null;
  }
};


/**
 * @return {ydn.db.base.TransactionMode?} return next transaction mode.
 */
ydn.db.tr.Serial.prototype.peekMode = function() {
  if (this.trQueue_.length > 0) {
    return this.trQueue_[0].mode;
  } else {
    return null;
  }
};


/**
 * Check next transaction.
 * @protected
 * @return {boolean}
 */
ydn.db.tr.Serial.prototype.isNextTxCompatible = function() {
  var scopes = this.peekScopes();
  var mode = this.peekMode();
  if (goog.isDefAndNotNull(scopes) && goog.isDefAndNotNull(mode)) {
    return this.reusedTx(scopes, mode);
  } else {
    return false;
  }
};


/**
 * Push a transaction job to the queue.
 * @param {Function} trFn function that invoke in the transaction.
 * @param {!Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_on_completed
 * handler.
 * @protected
 */
ydn.db.tr.Serial.prototype.pushTxQueue = function(trFn, store_names,
    opt_mode, opt_on_completed) {
  this.logger.finest('push tx queue[' + this.trQueue_.length + ']');
  this.trQueue_.push({
    fnc: trFn,
    store_names: store_names,
    mode: opt_mode,
    oncompleted: opt_on_completed
  });

};


/**
 * Abort an active transaction.
 */
ydn.db.tr.Serial.prototype.abort = function() {
  this.logger.finer(this + ': aborting');
  ydn.db.tr.Thread.abort(this.s_request_tx);
};


/**
 * Create a new isolated transaction. After creating a transaction, use
 * {@link #getTx} to received an active transaction. If transaction is not
 * active, it return null. In this case a new transaction must re-create.
 * @param {Function} trFn function that invoke in the transaction.
 * @param {!Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_on_completed
 * handler.
 */
ydn.db.tr.Serial.prototype.processTx = function(trFn, store_names, opt_mode,
                                                opt_on_completed) {

  var names = goog.isString(store_names) ? [store_names] : store_names;
  if (goog.DEBUG) {
    if (!goog.isArrayLike(names)) { // could be  DOMStringList or Array
      throw new ydn.debug.error.ArgumentException(
          'store names must be an array');
    } else if (names.length == 0) {
      throw new ydn.debug.error.ArgumentException(
          'number of store names must more than 0');
    } else {
      for (var i = 0; i < names.length; i++) {
        if (!goog.isString(names[i])) {
          throw new ydn.debug.error.ArgumentException('store name at ' + i +
              ' must be string but found ' + names[i] +
              ' of type ' + typeof names[i]);
        } else if (this.scope_store_names_) {
          if (!goog.array.contains(this.scope_store_names_, names[i])) {
            throw new ydn.debug.error.ArgumentException('store name "' + i +
                names[i] + '" in scope of ' + this);
          }
        } else {
          // todo: check with auto schema
          /*
          if (!this.getStorage().hasStore(names[i])) {
            throw new ydn.debug.error.ArgumentException('store name "' + i +
                names[i] + '" in the schema.');
          }
          */
        }
      }
    }
  }

  var mode = goog.isDef(opt_mode) ?
      opt_mode : ydn.db.base.TransactionMode.READ_ONLY;

  var me = this;

  if (this.mu_tx_.isActive() || // we are serial, one tx at a time
      // if db is not ready and we already send one tx request, we keep
      // our tx request in our queue
      (!this.getStorage().isReady() && // if not ready
          this.has_tx_started_ // we put only one tx, to prevent overlap.
          )) {
    this.pushTxQueue(trFn, store_names, mode, opt_on_completed);
  } else {
    var label = this.getLabel();
    var transaction_process = function(tx) {
      me.mu_tx_.up(tx, store_names, mode);
      label = me.getLabel();
      me.logger.fine(label + ' BEGIN ' +
          ydn.json.stringify(store_names) + ' ' + mode);

      // now execute transaction process
      trFn(me);
      trFn = null;

      me.mu_tx_.out(); // flag transaction callback scope is over.
      // transaction is still active and use in followup request handlers

      while (me.isNextTxCompatible()) {
        var task = me.trQueue_.shift();
        if (task.oncompleted) {
          me.completed_handlers_.push(task.oncompleted);
        }
        me.logger.finest('pop tx queue' + (me.trQueue_.length + 1) +
            ' reusing T' + me.getTxNo());
        task.fnc();
      }
    };

    var completed_handler = function(type, event) {
      //console.log('transaction_process ' + scope_name + ' completed.');
      me.logger.fine(label + ' ' + type);
      me.mu_tx_.down(type, event);
      for (var j = 0; j < me.completed_handlers_.length; j++) {
        var fn = me.completed_handlers_[j];
        fn(type, event);
      }
      me.completed_handlers_.length = 0;
      me.popTxQueue_();
      me.r_no_ = 0;
    };

    if (opt_on_completed) {
      this.completed_handlers_.push(opt_on_completed);
    }

    if (this.max_tx_no_ && this.getTxNo() >= this.max_tx_no_) {
      throw new ydn.debug.error.InvalidOperationException(
          'Exceed maximum number of transactions of ' + this.max_tx_no_);
    }

    this.has_tx_started_ = true;
    this.getStorage().transaction(transaction_process, names, mode,
        completed_handler);
  }

};


/**
 *
 * @return {string} return label.
 */
ydn.db.tr.Serial.prototype.getLabel = function() {
  return this.mu_tx_.getLabel();
};


/**
 * @inheritDoc
 */
ydn.db.tr.Serial.prototype.request = function(method, store_names, opt_mode,
    opt_on_complete) {
  var req = goog.base(this, 'request', method, store_names, opt_mode,
      opt_on_complete);
  var mode = opt_mode || ydn.db.base.TransactionMode.READ_ONLY;
  var me = this;

  if (this.mu_tx_.isActiveAndAvailable() && this.reusedTx(store_names, mode)) {
    //console.log(mu_tx.getScope() + ' continuing tx for ' + scope);
    // call within a transaction
    // continue to use existing transaction
    var tx = this.mu_tx_.getTx();
    this.r_no_++;
    req.setTx(tx, this.getLabel() + 'R' + this.r_no_);
    if (opt_on_complete) {
      this.completed_handlers_.push(opt_on_complete);
    }
  } else {
    //
    //
    /**
     * create a new transaction and close for invoke in non-transaction context
     * @param {Function} cb callback to process tx.
     */
    var tx_callback = function(cb) {
      //console.log('tx running for ' + scope);
      // me.not_ready_ = true;
      // transaction should be active now
      var tx = me.mu_tx_.getTx();
      me.r_no_++;
      req.setTx(tx, me.getLabel() + 'R' + me.r_no_);
    };
    me.processTx(tx_callback, store_names, mode, opt_on_complete);
  }
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.tr.Serial.prototype.exec = function(df, callback,
    store_names, opt_mode, on_complete) {
  var mode = opt_mode || ydn.db.base.TransactionMode.READ_ONLY;
  var me = this;
  var rq_label;

  if (me.mu_tx_.isActiveAndAvailable() && this.reusedTx(store_names, mode)) {
    //console.log(mu_tx.getScope() + ' continuing tx for ' + scope);
    // call within a transaction
    // continue to use existing transaction
    var tx = me.mu_tx_.getTx();
    /**
     * @param {*} result result.
     * @param {boolean=} opt_is_error true if request has error.
     */
    var resultCallback = function(result, opt_is_error) {
      me.s_request_tx = tx; // so that we can abort it.
      if (opt_is_error) {
        me.logger.finer(rq_label + ' ERROR');
        df.errback(result);
      } else {
        me.logger.finer(rq_label + ' SUCCESS');
        df.callback(result);
      }
      me.s_request_tx = null;
    };
    me.r_no_++;
    rq_label = me.getLabel() + 'R' + me.r_no_;
    me.logger.finer(rq_label + ' BEGIN');
    callback(tx, rq_label, resultCallback);
    me.logger.finer(rq_label + ' END');
    callback = null;
  } else {
    //
    //
    /**
     * create a new transaction and close for invoke in non-transaction context
     * @param {Function} cb callback to process tx.
     */
    var tx_callback = function(cb) {
      //console.log('tx running for ' + scope);
      // me.not_ready_ = true;
      // transaction should be active now
      var tx = me.mu_tx_.getTx();
      /**
       * @param {*} result result.
       * @param {boolean=} opt_is_error true if request has error.
       */
      var resultCallback2 = function(result, opt_is_error) {
        me.s_request_tx = tx; // so that we can abort it.
        if (opt_is_error) {
          me.logger.finer(rq_label + ' ERROR');
          df.errback(result);
        } else {
          me.logger.finer(rq_label + ' SUCCESS');
          df.callback(result);
        }
        me.s_request_tx = null;
      };
      me.r_no_++;
      rq_label = me.getLabel() + 'R' + me.r_no_;
      me.logger.finer(rq_label + ' BEGIN');
      callback(tx, rq_label, resultCallback2);
      me.logger.finer(rq_label + ' END');
      callback = null; // we don't call again.
    };
    me.processTx(tx_callback, store_names, mode, on_complete);
  }
};


/**
 * @final
 * @return {string} database name.
 */
ydn.db.tr.Serial.prototype.getName = function() {
  return this.getStorage().getName();
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.Serial.prototype.toString = function() {
    var s = !!this.s_request_tx ? '*' : '';
    return 'Serial' + ':' + this.getLabel() + s;
  };
}


// Input 49
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Transaction queue.
 *
 * A transaction is used to crate non-overlapping transaction so that each
 * database methods are atomic and run in order.
 */


goog.provide('ydn.db.tr.AtomicSerial');
goog.require('ydn.db.tr.Serial');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Create transaction queue providing methods to run in non-overlapping
 * transactions.
 *
 * @param {!ydn.db.tr.Storage} storage base storage.
 * @param {number} ptx_no transaction queue number.
 * @constructor
 * @extends {ydn.db.tr.Serial}
 */
ydn.db.tr.AtomicSerial = function(storage, ptx_no) {

  goog.base(this, storage, ptx_no);

};
goog.inherits(ydn.db.tr.AtomicSerial, ydn.db.tr.Serial);


/**
 * @const
 * @type {boolean}
 */
ydn.db.tr.AtomicSerial.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.AtomicSerial.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.AtomicSerial');


/**
 * @inheritDoc
 */
ydn.db.tr.AtomicSerial.prototype.request = function(method, scope, opt_mode) {
  var req_setDbValue, result, is_error;

  /**
   * @param {ydn.db.base.TxEventTypes} t event type.
   * @param {*} e error.
   */
  var onComplete = function(t, e) {
    // console.log('onComplete', t, result);
    req.removeTx();
    if (req_setDbValue) {
      if (t != ydn.db.base.TxEventTypes.COMPLETE) {
        is_error = true;
        result = e;
      }
      req_setDbValue(result, is_error);
    } else {
      var err = new ydn.db.TimeoutError();
      req.setDbValue(err, true);
    }
  };
  var req = goog.base(this, 'request', method, scope, opt_mode, onComplete);
  // intersect request result to make atomic

  req.await(function(value, has_error, rtn) {
    // console.log('req success', value);
    is_error = has_error;
    result = value;
    req_setDbValue = rtn;
  });
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.tr.AtomicSerial.prototype.exec = function(df, callback, store_names,
                                                 mode, on_completed) {

  // intersect request result to make atomic
  var result;
  var is_error;
  var cdf = new goog.async.Deferred();
  cdf.addCallbacks(function(x) {
    is_error = false;
    result = x;
  }, function(e) {
    is_error = true;
    result = e;
  });
  var completed_handler = function(t, e) {
    if (t != ydn.db.base.TxEventTypes.COMPLETE) {
      df.errback(e);
    } else if (is_error === true) {
      df.errback(result);
    } else if (is_error === false) {
      df.callback(result);
    } else {
      var err = new ydn.db.TimeoutError();
      df.errback(err);
    }
    if (on_completed) {
      on_completed(t, e);
      on_completed = undefined;
    }
  };
  goog.base(this, 'exec', cdf, callback, store_names, mode, completed_handler);
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.AtomicSerial.prototype.toString = function() {
    return 'Atomic' + goog.base(this, 'toString');
  };
}


// Input 50
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Provide atomic CRUD database operations on a transaction queue.
 *
 *
 */


goog.provide('ydn.db.tr.DbOperator');
goog.require('ydn.db.crud.IOperator');
goog.require('ydn.db.tr.AtomicSerial');
goog.require('ydn.db.tr.Thread');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Construct storage to execute CRUD database operations.
 *
 * Execution database operation is atomic, if a new transaction require,
 * otherwise existing transaction is used and the operation become part of
 * the existing transaction. A new transaction is required if the transaction
 * is not active or locked. Active transaction can be locked by using
 * mutex.
 *
 * @param {!ydn.db.tr.Storage} storage base storage object.
 * @param {!ydn.db.schema.Database} schema
 * @param {ydn.db.tr.Thread} tx_thread
 * @param {ydn.db.tr.Thread} sync_thread
 * @constructor
 * @struct
 */
ydn.db.tr.DbOperator = function(storage, schema, tx_thread, sync_thread) {

  /**
   * @final
   * @type {!ydn.db.tr.Storage}
   * @private
   */
  this.storage_ = storage;

  /**
   * @protected
   * @final
   * @type {!ydn.db.schema.Database}
   */
  this.schema = schema;

  /**
   * @final
   */
  this.tx_thread = tx_thread;

  /**
   * @final
   */
  this.sync_thread = sync_thread;

  this.executor = null;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.DbOperator.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.DbOperator');


/**
 * @type {ydn.db.crud.req.IRequestExecutor}
 * @protected
 */
ydn.db.tr.DbOperator.prototype.executor;


/**
 * @type {ydn.db.tr.Thread}
 * @protected
 */
ydn.db.tr.DbOperator.prototype.tx_thread;


/**
 * @type {ydn.db.tr.Thread}
 * @protected
 */
ydn.db.tr.DbOperator.prototype.sync_thread;


/**
 * @final
 * @return {number}
 */
ydn.db.tr.DbOperator.prototype.getTxNo = function() {
  return this.tx_thread.getTxNo();
};


/**
 * Abort an active transaction.
 */
ydn.db.tr.DbOperator.prototype.abort = function() {
  this.tx_thread.abort();
};


/**
 * @final
 * @return {ydn.db.crud.req.IRequestExecutor}
 */
ydn.db.tr.DbOperator.prototype.getExecutor = function() {
  if (!this.executor) {
    this.executor = this.storage_.newExecutor();
  }

  return this.executor;
};


/**
 * @final
 * @return {!ydn.db.tr.Storage} storage.
 */
ydn.db.tr.DbOperator.prototype.getStorage = function() {
  return this.storage_;
};


/**
 *  Commit active transaction in coroutine thread.
 */
ydn.db.tr.DbOperator.prototype.commit = function() {
  this.tx_thread.commit();
};


/**
 * Add or update a store issuing a version change event.
 * @protected
 * @param {!StoreSchema|!ydn.db.schema.Store} store schema.
 * @return {!goog.async.Deferred} promise.
 */
ydn.db.tr.DbOperator.prototype.addStoreSchema = function(store) {
  return this.getStorage().addStoreSchema(store);
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.DbOperator.prototype.toString = function() {
    var s = 'TxStorage:' + this.getStorage().getName();
    return s;
  };
}





// Input 51
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Provide atomic CRUD database operations on a transaction queue.
*
* @author kyawtun@yathit.com (Kyaw Tun)
*/


goog.provide('ydn.db.crud.DbOperator');
goog.require('goog.debug.Logger');
goog.require('goog.userAgent');
goog.require('ydn.db');
goog.require('ydn.db.Key');
goog.require('ydn.db.Request');
goog.require('ydn.db.crud.IOperator');
goog.require('ydn.db.tr.AtomicSerial');
goog.require('ydn.db.tr.DbOperator');
goog.require('ydn.db.tr.Thread');
goog.require('ydn.debug.error.ArgumentException');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Construct storage to execute CRUD database operations.
 *
 * Execution database operation is atomic, if a new transaction require,
 * otherwise existing transaction is used and the operation become part of
 * the existing transaction. A new transaction is required if the transaction
 * is not active or locked. Active transaction can be locked by using
 * mutex.
 *
 * @param {!ydn.db.crud.Storage} storage base storage object.
 * @param {!ydn.db.schema.Database} schema schema.
 * @param {ydn.db.tr.Thread} tx_thread
 * @param {ydn.db.tr.Thread} sync_thread
 * @implements {ydn.db.crud.IOperator}
 * @constructor
 * @extends {ydn.db.tr.DbOperator}
 * @struct
*/
ydn.db.crud.DbOperator = function(storage, schema, tx_thread, sync_thread) {
  goog.base(this, storage, schema, tx_thread, sync_thread);
};
goog.inherits(ydn.db.crud.DbOperator, ydn.db.tr.DbOperator);


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.crud.DbOperator.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.crud.DbOperator');


/**
 * @return {ydn.db.crud.req.IRequestExecutor} executor.
 */
ydn.db.crud.DbOperator.prototype.getCrudExecutor = function() {
  return /** @type {ydn.db.crud.req.IRequestExecutor} */ (this.getExecutor());
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.count = function(store_name, index_or_keyrange,
                                                 index_key_range, unique) {
  var req;
  var me = this;

  /**
   * @type {!Array.<string>}
   */
  var store_names;

  /**
   * @type {string}
   */
  var index_name;
  /**
   * @type {IDBKeyRange}
   */
  var key_range;

  if (!goog.isDefAndNotNull(store_name)) {
    this.logger.warning('count method requires store name(s)');
    var stores = this.schema.getStoreNames();
    req = this.tx_thread.request(ydn.db.Request.Method.COUNT, stores);
    req.await(function(cnt, is_error, cb) {
      if (is_error) {
        cb(cnt, true);
        return;
      }
      var total = 0;
      for (var i = 0; i < cnt.length; i++) {
        total += cnt[i];
      }
      cb(total, false);
    }, this);
    req.addTxback(function() {
      //console.log('counting');
      this.getCrudExecutor().countStores(req, store_names);
    }, this);
  } else if (goog.isArray(store_name)) {

    if (goog.isDef(index_key_range) || goog.isDef(index_or_keyrange)) {
      throw new ydn.debug.error.ArgumentException('too many arguments.');
    }

    store_names = store_name;
    for (var i = 0; i < store_names.length; i++) {
      if (!this.schema.hasStore(store_names[i])) {
        throw new ydn.debug.error.ArgumentException('store name "' +
            store_names[i] + '" at ' + i + ' not found.');
      }
    }

    //console.log('waiting to count');
    this.logger.finer('countStores: ' + ydn.json.stringify(store_names));
    req = this.tx_thread.request(ydn.db.Request.Method.COUNT, store_names);

    req.addTxback(function() {
      //console.log('counting');
      this.getCrudExecutor().countStores(req, store_names);
    }, this);
  } else if (goog.isString(store_name)) {
    var store = this.schema.getStore(store_name);
    if (!store) {
      throw new ydn.debug.error.ArgumentException('store name "' + store_name +
          '" not found.');
    }
    if (goog.DEBUG && goog.isDef(unique) && !goog.isBoolean(unique)) {
      throw new ydn.debug.error.ArgumentException('unique value "' + unique +
          '" must be boolean, but found ' + typeof unique + '.');
    }
    store_names = [store_name];

    if (goog.isString(index_or_keyrange)) {
      // index key range count.
      index_name = index_or_keyrange;

      if (goog.isObject(index_key_range)) {
        if (goog.DEBUG) {
          var msg1 = ydn.db.KeyRange.validate(index_key_range);
          if (msg1) {
            throw new ydn.debug.error.ArgumentException('invalid key range: ' +
                ydn.json.toShortString(index_key_range) + ' ' + msg1);
          }
        }
        key_range = ydn.db.KeyRange.parseIDBKeyRange(index_key_range);
      } else {
        if (goog.DEBUG && goog.isDefAndNotNull(index_key_range)) {
          throw new ydn.debug.error.ArgumentException('invalid key range: ' +
              ydn.json.toShortString(index_key_range) +
              ' of type ' + typeof index_key_range);
        }
        key_range = null;
      }
    } else if (goog.isObject(index_or_keyrange) ||
        !goog.isDefAndNotNull(index_or_keyrange)) {

      if (goog.isObject(index_or_keyrange)) {
        if (goog.DEBUG) {
          var msg = ydn.db.KeyRange.validate(index_or_keyrange);
          if (msg) {
            throw new ydn.debug.error.ArgumentException('invalid key range: ' +
                ydn.json.toShortString(index_or_keyrange) + ' ' + msg);
          }
        }
        key_range = ydn.db.KeyRange.parseIDBKeyRange(index_or_keyrange);
      } else {
        if (goog.isDefAndNotNull(index_or_keyrange)) {
          throw new ydn.debug.error.ArgumentException('key range must be ' +
              ' an object but found ' +
              ydn.json.toShortString(index_or_keyrange) + ' of type ' +
              typeof index_or_keyrange);
        }
        key_range = null;
      }
    } else {
      throw new ydn.debug.error.ArgumentException('invalid second argument ' +
          'for count "' + ydn.json.toShortString(index_key_range) +
          '" of type ' + typeof index_or_keyrange);
    }

    this.logger.finer('countKeyRange: ' + store_name + ' ' +
        (index_name ? index_name : '') + ydn.json.stringify(key_range));
    req = this.tx_thread.request(ydn.db.Request.Method.COUNT, store_names);
    store.hook(req, arguments);
    req.addTxback(function(tx) {
      this.getCrudExecutor().countKeyRange(req, store_names[0], key_range,
          index_name, !!unique);
    }, this);

  } else {
    throw new ydn.debug.error.ArgumentException(
        'Invalid store name or store names.');
  }

  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.get = function(arg1, arg2) {

  var me = this;
  var req;

  if (arg1 instanceof ydn.db.Key) {
    /**
     * @type {ydn.db.Key}
     */
    var k = arg1;
    var k_store_name = k.getStoreName();
    var store = this.schema.getStore(k_store_name);
    if (!store) {
      if (this.schema.isAutoSchema()) {
        if (this.getStorage().isReady()) {
          return ydn.db.Request.succeed(ydn.db.Request.Method.GET, undefined);
        } else {
          req = new ydn.db.Request(ydn.db.Request.Method.GET);
          this.getStorage().onReady(function() {
            me.get(arg1, arg2).addCallbacks(function(x) {
              req.callback(x);
            }, function(e) {
              req.errback(e);
            });
          });
          return req;
        }
      } else {
        throw new ydn.debug.error.ArgumentException('Store: ' +
            k_store_name + ' not found.');
      }
    }

    var kid = k.getId();
    this.logger.finer('getByKey: ' + k_store_name + ':' + kid);
    req = this.tx_thread.request(ydn.db.Request.Method.GET_BY_KEY,
        [k_store_name]);
    store.hook(req, arguments);
    req.addTxback(function() {
      this.getCrudExecutor().getById(req, k_store_name, kid);
    }, this);
  } else if (goog.isString(arg1) && goog.isDef(arg2)) {
    var store_name = arg1;
    var store = this.schema.getStore(store_name);
    if (!store) {
      if (this.schema.isAutoSchema()) {
        if (this.getStorage().isReady()) {
          return ydn.db.Request.succeed(ydn.db.Request.Method.GET, undefined);
        } else {
          req = new ydn.db.Request(ydn.db.Request.Method.GET);
          this.getStorage().onReady(function() {
            me.get(arg1, arg2).addCallbacks(function(x) {
              req.callback(x);
            }, function(e) {
              req.errback(e);
            });
          });
          return req;
        }
      } else {
        throw new ydn.debug.error.ArgumentException('Store name "' +
            store_name + '" not found.');
      }
    }
    var id = arg2;
    goog.asserts.assert(ydn.db.Key.isValidKey(id), 'key ' + id + ' of type ' +
        (typeof id) + ' is not a valid key');
    this.logger.finer('getById: ' + store_name + ':' + id);
    req = this.tx_thread.request(ydn.db.Request.Method.GET, [store_name]);
    store.hook(req, arguments);
    req.addTxback(function() {
      this.getCrudExecutor().getById(req, store_name, /** @type {IDBKey} */ (id));
    }, this);

  } else {
    throw new ydn.debug.error.ArgumentException(
        'get require valid input arguments.');
  }

  return req;
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.keys = function(opt_store_name, arg1,
                                                 arg2, arg3, arg4, arg5, arg6) {
  var me = this;

  /**
   * @type {number}
   */
  var limit;
  /**
   * @type {number}
   */
  var offset;
  /**
   * @type {ydn.db.IDBKeyRange}
   */
  var range = null;
  /**
   * @type {boolean}
   */
  var reverse = false;
  /**
   * @type {boolean}
   */
  var unique = false;
  /**
   *
   * @type {string}
   */
  var store_name = /** @type {string} */ (opt_store_name);

  var store = this.schema.getStore(store_name);

  if (goog.DEBUG) {
    if (!goog.isString(store_name)) {
      throw new ydn.debug.error.ArgumentException(
          'store name must be a string, ' +
          'but ' + store_name + ' of type ' + typeof store_name + ' is not.');
    }
    if (!this.schema.isAutoSchema()) {
      if (!store) {
        throw new ydn.debug.error.ArgumentException('store name "' +
            store_name + '" not found.');
      }
      if (goog.isString(arg1)) {
        var index = store.getIndex(arg1);
        if (!index) {
          throw new ydn.debug.error.ArgumentException('index "' + arg1 +
              '" not found in store "' + store_name + '".');
        }
      }
    }
  }

  if (this.schema.isAutoSchema() && !store) {
    return ydn.db.Request.succeed(ydn.db.Request.Method.KEYS, []);
  }

  var req;

  if (goog.isString(arg1)) { // index key range
    var index_name = arg1;
    if (goog.DEBUG) {
      var msg = ydn.db.KeyRange.validate(/** @type {KeyRangeJson} */ (arg2));
      if (msg) {
        throw new ydn.debug.error.ArgumentException('invalid key range: ' +
            arg2 + ' ' + msg);
      }
    }
    range = ydn.db.KeyRange.parseIDBKeyRange(
        /** @type {KeyRangeJson} */ (arg2));

    if (goog.isNumber(arg3)) {
      limit = arg3;
    } else if (!goog.isDef(arg3)) {
      limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
    } else {
      throw new ydn.debug.error.ArgumentException('limit must be a number');
    }
    if (goog.isNumber(arg4)) {
      offset = arg4;
    } else if (!goog.isDef(arg4)) {
      offset = 0;
    } else {
      throw new ydn.debug.error.ArgumentException('offset must be a number');
    }
    if (goog.isDef(arg5)) {
      if (goog.isBoolean(arg5)) {
        reverse = arg5;
      } else {
        throw new ydn.debug.error.ArgumentException(
            'reverse must be a boolean');
      }
    }
    if (goog.isDef(arg6)) {
      if (goog.isBoolean(arg6)) {
        unique = arg6;
      } else {
        throw new ydn.debug.error.ArgumentException(
            'unique must be a boolean');
      }
    }
    this.logger.finer('keysByIndexKeyRange: ' + store_name);
    req = this.tx_thread.request(ydn.db.Request.Method.KEYS_INDEX,
        [store_name]);
    store.hook(req, arguments);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_PRIMARY_KEY,
          store_name, index_name, range, limit, offset, reverse, unique);
    }, this);
  } else {
    if (goog.isObject(arg1)) {
      if (goog.DEBUG) {
        var msg = ydn.db.KeyRange.validate(arg1);
        if (msg) {
          throw new ydn.debug.error.ArgumentException('invalid key range: ' +
              ydn.json.toShortString(arg1) + ' ' + msg);
        }
      }
      range = ydn.db.KeyRange.parseIDBKeyRange(arg1);
    } else {
      if (goog.DEBUG && goog.isDefAndNotNull(arg1)) {
        throw new ydn.debug.error.ArgumentException('invalid key range: ' +
            ydn.json.toShortString(arg1) + ' of type ' + typeof arg1);
      }
      range = null;
    }
    if (goog.isNumber(arg2)) {
      limit = arg2;
    } else if (!goog.isDef(arg2)) {
      limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
    } else {
      throw new ydn.debug.error.ArgumentException('limit must be a number');
    }
    if (goog.isNumber(arg3)) {
      offset = arg3;
    } else if (!goog.isDef(arg3)) {
      offset = 0;
    } else {
      throw new ydn.debug.error.ArgumentException('offset must be a number');
    }
    if (goog.isDef(arg4)) {
      if (goog.isBoolean(arg4)) {
        reverse = arg4;
      } else {
        throw new ydn.debug.error.ArgumentException(
            'reverse must be a boolean');
      }
    }
    this.logger.finer('keysByKeyRange: ' + store_name);
    req = this.tx_thread.request(ydn.db.Request.Method.KEYS, [store_name]);
    store.hook(req, arguments);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_PRIMARY_KEY,
          store_name, null, range, limit, offset, reverse, false);
    }, this);
  }

  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.values = function(arg0, arg1, arg2, arg3, arg4,
                                                   arg5, arg6) {

  var me = this;
  var req;
  var method = ydn.db.Request.Method.NONE;

  /**
   * @type {number}
   */
  var limit;
  /**
   * @type {number}
   */
  var offset;
  /**
   * @type {boolean}
   */
  var reverse = false;
  /**
   * @type {boolean}
   */
  var unique = false;

  if (goog.isString(arg0)) {
    var store_name = arg0;
    var store = this.schema.getStore(store_name);
    if (!store) {
      if (this.schema.isAutoSchema()) {
        if (this.getStorage().isReady()) {
          return ydn.db.Request.succeed(ydn.db.Request.Method.VALUES, []);
        } else {
          req = new ydn.db.Request(ydn.db.Request.Method.VALUES);
          this.getStorage().onReady(function() {
            me.values(arg0, arg1, arg2, arg3, arg4,
                    arg5).addCallbacks(function(x) {
              req.callback(x);
            }, function(e) {
              req.errback(e);
            })
          });
          return req;
        }
      } else {
        throw new ydn.db.NotFoundError(store_name);
      }
    }

    if (goog.isArray(arg1)) {
      if (goog.DEBUG && (goog.isDef(arg2) || goog.isDef(arg3))) {
        throw new ydn.debug.error.ArgumentException('too many input arguments');
      }
      var ids = arg1;
      this.logger.finer('listByIds: ' + store_name + ' ' +
          ids.length + ' ids');
      req = this.tx_thread.request(ydn.db.Request.Method.VALUES_IDS,
          [store_name]);
      store.hook(req, arguments);
      req.addTxback(function() {
        this.getCrudExecutor().listByIds(req, store_name, ids);
      }, this);
    } else if (goog.isString(arg1)) { // index name
      var index_name = arg1;
      if (goog.DEBUG) {
        if (!store.hasIndex(index_name)) {
          throw new ydn.debug.error.ArgumentException('index "' +
              index_name + '" not found in store "' + store_name + '"');
        }
        var msg = ydn.db.KeyRange.validate(/** @type {KeyRangeJson} */ (arg2));
        if (msg) {
          throw new ydn.debug.error.ArgumentException('invalid key range: ' +
              arg2 + ' ' + msg);
        }
      }
      var range = ydn.db.KeyRange.parseIDBKeyRange(
          /** @type {KeyRangeJson} */ (arg2));
      if (!goog.isDef(arg3)) {
        limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
      } else if (goog.isNumber(arg3)) {
        limit = arg3;
      } else {
        throw new ydn.debug.error.ArgumentException('limit must be a number.');
      }
      if (!goog.isDef(arg4)) {
        offset = 0;
      } else if (goog.isNumber(arg4)) {
        offset = arg4;
      } else {
        throw new ydn.debug.error.ArgumentException('offset must be a number.');
      }
      if (goog.isBoolean(arg5)) {
        reverse = arg5;
      } else if (goog.isDef(arg5)) {
        throw new ydn.debug.error.ArgumentException(
            'reverse must be a boolean, but ' + arg5);
      }
      if (goog.isDef(arg6)) {
        if (goog.isBoolean(arg6)) {
          unique = arg6;
        } else {
          throw new ydn.debug.error.ArgumentException(
              'unique must be a boolean');
        }
      }
      this.logger.finer('listByIndexKeyRange: ' + store_name + ':' +
          index_name);
      method = ydn.db.Request.Method.VALUES_INDEX;
      req = this.tx_thread.request(method, [store_name]);
      store.hook(req, arguments);
      req.addTxback(function() {
        this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_VALUE,
            store_name, index_name, range, limit, offset, reverse, unique);
      }, this);
    } else {
      var range = null;
      if (goog.isObject(arg1)) {
        if (goog.DEBUG) {
          var msg = ydn.db.KeyRange.validate(arg1);
          if (msg) {
            throw new ydn.debug.error.ArgumentException('invalid key range: ' +
                arg1 + ' ' + msg);
          }
        }
        range = ydn.db.KeyRange.parseIDBKeyRange(arg1);
      } else if (goog.DEBUG && goog.isDefAndNotNull(arg1)) {
        throw new ydn.debug.error.ArgumentException('expect key range object,' +
            ' but found "' + ydn.json.toShortString(arg1) + '" of type ' + typeof arg1);
      }
      if (!goog.isDef(arg2)) {
        limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
      } else if (goog.isNumber(arg2)) {
        limit = arg2;
      } else {
        throw new ydn.debug.error.ArgumentException('limit must be a number, ' +
            'but ' + arg2 + ' is ' + typeof arg2);
      }
      if (!goog.isDef(arg3)) {
        offset = 0;
      } else if (goog.isNumber(arg3)) {
        offset = arg3;
      } else {
        throw new ydn.debug.error.ArgumentException(
            'offset must be a number, ' + 'but ' + arg3 + ' is ' + typeof arg3);
      }
      if (goog.isDef(arg4)) {
        if (goog.isBoolean(arg4)) {
          reverse = arg4;
        } else {
          throw new ydn.debug.error.ArgumentException('reverse must be a ' +
              'boolean, but ' + arg4 + ' is ' + typeof arg4);
        }
      }
      this.logger.finer((range ? 'listByKeyRange: ' : 'listByStore: ') +
          store_name);
      method = ydn.db.Request.Method.VALUES;
      req = this.tx_thread.request(method, [store_name]);
      store.hook(req, arguments);
      req.addTxback(function() {
        this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_VALUE,
            store_name, null, range, limit, offset, reverse, false);
      }, this);
    }
  } else if (goog.isArray(arg0)) {
    if (arg0[0] instanceof ydn.db.Key) {
      var store_names = [];
      /**
       * @type {!Array.<!ydn.db.Key>}
       */
      var keys = /** @type {!Array.<!ydn.db.Key>} */ (arg0);
      for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        var i_store_name = key.getStoreName();
        if (!this.schema.hasStore(i_store_name)) {
          if (this.schema.isAutoSchema()) {
            var fail_array = [];
            // I think more efficient than: fail_array.length = keys.length;
            fail_array[keys.length - 1] = undefined;
            return ydn.db.Request.succeed(ydn.db.Request.Method.GET,
                fail_array);
          } else {
            throw new ydn.debug.error.ArgumentException('Store: ' +
                i_store_name + ' not found.');
          }
        }
        if (!goog.array.contains(store_names, i_store_name)) {
          store_names.push(i_store_name);
        }
      }
      this.logger.finer('listByKeys: ' + ydn.json.stringify(store_names) +
          ' ' + keys.length + ' keys');
      req = this.tx_thread.request(ydn.db.Request.Method.VALUES_KEYS,
          store_names);
      req.addTxback(function() {
        this.getCrudExecutor().listByKeys(req, keys);
      }, this);
    } else {
      throw new ydn.debug.error.ArgumentException('first argument' +
          'must be array of ydn.db.Key, but ' + arg0[0] + ' of ' +
          typeof arg0[0] + ' found.');
    }
  } else {
    throw new ydn.debug.error.ArgumentException('first argument ' + arg0 +
        ' is invalid.');
  }

  return req;
};


/**
 * List
 * @param {ydn.db.base.QueryMethod} type
 * @param {string} store_name
 * @param {string=} opt_index
 * @param {ydn.db.KeyRange|ydn.db.IDBKeyRange=} opt_key_range
 * @param {number=} opt_limit
 * @param {number=} opt_offset
 * @param {boolean=} opt_reverse
 * @param {boolean=} opt_unique
 * @param {Array.<IDBKey|undefined>=} opt_pos last cursor position.
 * @return {!ydn.db.Request}
 */
ydn.db.crud.DbOperator.prototype.list = function(type, store_name, opt_index,
    opt_key_range, opt_limit, opt_offset, opt_reverse, opt_unique, opt_pos) {

  var store = this.schema.getStore(store_name);
  if (!store) {
    if (this.schema.isAutoSchema()) {
      return ydn.db.Request.succeed(ydn.db.Request.Method.GET, []);
    } else {
      throw new ydn.db.NotFoundError(store_name);
    }
  }

  var me = this;
  var req;
  var method = ydn.db.Request.Method.NONE;

  if (goog.DEBUG) {
    if (opt_index && !store.hasIndex(opt_index)) {
      throw new ydn.debug.error.ArgumentException('index "' +
          opt_index + '" not found in store "' + store_name + '"');
    }
    var msg = ydn.db.KeyRange.validate(opt_key_range);
    if (msg) {
      throw new ydn.debug.error.ArgumentException('invalid key range: ' +
          opt_key_range + ' ' + msg);
    }
  }
  var range = ydn.db.KeyRange.parseIDBKeyRange(opt_key_range);
  var limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
  if (goog.isNumber(opt_limit)) {
    limit = opt_limit;
  } else if (goog.isDefAndNotNull(opt_limit)) {
    throw new ydn.debug.error.ArgumentException('limit must be a number but "' +
        opt_limit + '" of type ' + typeof opt_limit + ' found.');
  }
  var offset = 0;
  if (goog.isNumber(opt_offset)) {
    offset = opt_offset;
  } else if (goog.isDefAndNotNull(opt_offset)) {
    throw new ydn.debug.error.ArgumentException('offset must be a number but' +
        ' "' + opt_offset + '" of type ' + typeof opt_offset + ' found.');
  }
  var reverse = false;
  if (goog.isBoolean(opt_reverse)) {
    reverse = opt_reverse;
  } else if (goog.isDefAndNotNull(opt_reverse)) {
    throw new ydn.debug.error.ArgumentException('reverse must be a boolean ' +
        'but "' + opt_reverse + '" of type ' + typeof opt_reverse + ' found.');
  }
  var unique = false;
  if (goog.isBoolean(opt_unique)) {
    unique = opt_unique;
  } else if (goog.isDefAndNotNull(opt_unique)) {
    throw new ydn.debug.error.ArgumentException('unique must be a boolean but' +
        ' "' + opt_unique + '" of type ' + typeof opt_unique + ' found.');
  }
  if (offset && !!opt_pos && goog.isDef(opt_pos[0])) {
    throw new ydn.debug.error.ArgumentException('offset must not given when ' +
        'initial cursor position is defined');
  }
  this.logger.finer(type + ': ' + store_name + ':' + opt_index);
  method = ydn.db.Request.Method.VALUES_INDEX;
  req = this.tx_thread.request(method, [store_name]);
  // store.hook(req, arguments);
  req.addTxback(function() {
    this.getCrudExecutor().list(req, type, store_name,
        opt_index || null, range, limit, offset, reverse, unique, opt_pos);
  }, this);

  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.add = function(store_name_or_schema, value,
                                               opt_keys) {

  var store_name = goog.isString(store_name_or_schema) ?
      store_name_or_schema : goog.isObject(store_name_or_schema) ?
      store_name_or_schema['name'] : undefined;
  if (!goog.isString(store_name)) {
    throw new ydn.debug.error.ArgumentException('store name ' + store_name +
        ' must be a string, but ' + typeof store_name);
  }

  var store = this.schema.getStore(store_name);
  if (!store) {
    if (!this.schema.isAutoSchema()) {
      throw new ydn.debug.error.ArgumentException('store name "' + store_name +
          '" not found.');
    }
    var schema = goog.isObject(store_name_or_schema) ?
        store_name_or_schema : {'name': store_name};

    // this is async process, but we don't need to wait for it.
    store = ydn.db.schema.Store.fromJSON(/** @type {!StoreSchema} */ (schema));
    this.logger.finer('Adding object store: ' + store_name);
    this.addStoreSchema(store);

  } else if (this.schema.isAutoSchema() &&
      goog.isObject(store_name_or_schema)) {
    // if there is changes in schema, change accordingly.
    var new_schema = ydn.db.schema.Store.fromJSON(store_name_or_schema);
    var diff = store.difference(new_schema);
    if (diff) {
      throw new ydn.debug.error.NotSupportedException(diff);
      // this.addStoreSchema(store);
    }
  }

  var req;

  if (!store) {
    throw new ydn.debug.error.ArgumentException('store name "' + store_name +
        '" not found.');
  }
  // https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore#put
  if ((goog.isString(store.keyPath)) && goog.isDef(opt_keys)) {
    // The object store uses in-line keys or has a key generator, and a key
    // parameter was provided.
    throw new ydn.debug.error.ArgumentException(
        'key must not be provided while the store uses in-line key.');
  //} else if (store.autoIncrement && goog.isDef(opt_keys)) {
    // The object store uses in-line keys or has a key generator, and a key
    // parameter was provided.
  //  throw new ydn.debug.error.ArgumentException('key must not be provided ' +
  //      'while autoIncrement is true.');
  } else if (!store.usedInlineKey() && !store.autoIncrement &&
      !goog.isDef(opt_keys)) {
    // The object store uses out-of-line keys and has no key generator, and no
    // key parameter was provided.
    throw new ydn.debug.error.ArgumentException(
        'out-of-line key must be provided.');
  }

  if (goog.isArray(value)) {
    var objs = value;
    var keys = /** @type {!Array.<(number|string)>|undefined} */ (opt_keys);
    //console.log('waiting to putObjects');
    this.logger.finer('addObjects: ' + store_name + ' ' + objs.length +
        ' objects');

    for (var i = 0; i < objs.length; i++) {
      store.generateIndex(objs[i]);
    }
    req = this.tx_thread.request(ydn.db.Request.Method.ADDS,
        [store_name], ydn.db.base.TransactionMode.READ_WRITE);
    req.addTxback(function() {
      //console.log('putObjects');
      this.getCrudExecutor().insertObjects(req, false, false, store_name, objs,
          keys);
    }, this);

    if (store.dispatch_events) {
      req.addCallback(function(keys) {
        var event = new ydn.db.events.StoreEvent(ydn.db.events.Types.CREATED,
            this.getStorage(), store.getName(), keys, objs);
        this.getStorage().dispatchDbEvent(event);
      }, this);
    }

  } else if (goog.isObject(value)) {
    var obj = value;
    var key = /** @type {number|string|undefined} */ (opt_keys);
    var label = 'store: ' + store_name + ' key: ' + store.extractKey(obj, key);

    this.logger.finer('addObject: ' + label);
    store.generateIndex(obj);
    req = this.tx_thread.request(ydn.db.Request.Method.ADD,
        [store_name], ydn.db.base.TransactionMode.READ_WRITE);
    req.addTxback(function() {
      this.getCrudExecutor().insertObjects(req, false, true, store_name, [obj],
          [key]);
    }, this);

    if (store.dispatch_events) {
      req.addCallback(function(key) {
        var event = new ydn.db.events.RecordEvent(ydn.db.events.Types.CREATED,
            this.getStorage(), store.getName(), key, obj);
        this.getStorage().dispatchDbEvent(event);
      }, this);
    }

  } else {
    throw new ydn.debug.error.ArgumentException('record must be an object or ' +
        'array list of objects, but ' + value + ' of type ' + typeof value +
        ' found.');
  }

  return req;
};


/**
 *
 * @param {string|StoreSchema} store_name_schema store name or schema.
 * @return {ydn.db.schema.Store} store.
 * @private
 */
ydn.db.crud.DbOperator.prototype.getStore_ = function(store_name_schema) {

  var store_name = goog.isString(store_name_schema) ?
      store_name_schema : goog.isObject(store_name_schema) ?
      store_name_schema['name'] : undefined;
  if (!goog.isString(store_name)) {
    throw new ydn.debug.error.ArgumentException('store name must be a string');
  }

  var store = this.schema.getStore(store_name);
  if (!store) {
    if (!this.schema.isAutoSchema()) {
      throw new ydn.db.NotFoundError(store_name);
    }
    var schema = goog.isObject(store_name_schema) ?
        store_name_schema : {'name': store_name};

    // this is async process, but we don't need to wait for it.
    store = ydn.db.schema.Store.fromJSON(/** @type {!StoreSchema} */ (schema));
    this.logger.finer('Adding object store: ' + store_name);
    this.addStoreSchema(store);

  } else if (this.schema.isAutoSchema() && goog.isObject(store_name_schema))
  {
    // if there is changes in schema, change accordingly.
    var new_schema = ydn.db.schema.Store.fromJSON(store_name_schema);
    var diff = store.difference(new_schema);
    if (diff) {
      throw new ydn.debug.error.NotSupportedException(diff);
      // this.addStoreSchema(store);
    }
  }
  if (!store) {
    throw new ydn.db.NotFoundError(store_name);
  }
  return store;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.load = function(store_name_or_schema, data,
                                                 opt_delimiter) {

  var delimiter = opt_delimiter || ',';

  var store = this.getStore_(store_name_or_schema);
  var store_name = store.getName();

  var df =  this.tx_thread.request(ydn.db.Request.Method.LOAD, [store_name]);
  var me = this;

  this.tx_thread.exec(df, function(tx, tx_no, cb) {
    me.getCrudExecutor().putData(tx, tx_no, cb, store_name, data, delimiter);
  }, [store_name], ydn.db.base.TransactionMode.READ_WRITE);
  return df;
};


/**
 * Full text search.
 * @param {ydn.db.schema.fulltext.ResultSet} query
 * @return {!ydn.db.Request}
 */
ydn.db.crud.DbOperator.prototype.search = function(query) {
  var store_names = query.getStoreList();
  this.logger.finest('query ' + query);
  var req = this.tx_thread.request(ydn.db.Request.Method.SEARCH, store_names,
      ydn.db.base.TransactionMode.READ_ONLY);
  req.addTxback(function() {
    var exe = this.getCrudExecutor();
    // console.log('search ' + query);

    query.nextLookup(function(store_name, index_name, kr, entry) {
      var iReq = req.copy();
      // console.log(store_name, index_name, kr);
      exe.list(iReq, ydn.db.base.QueryMethod.LIST_VALUE, store_name, index_name,
          kr.toIDBKeyRange(), 100, 0, false, false);
      iReq.addBoth(function(x) {
        // console.log(store_name, index_name, kr.lower, x);
        var e = null;
        if (!(x instanceof Array)) {
          e = x;
          x = [];
        }
        var next = query.addResult(this, /** @type {Array} */ (x));
        if (next === true) {
          req.notify(query);
        } else if (next === false) {
          req.callback(query.collect());
        }
        if (e) {
          throw e;
        }
      }, entry);
    });
  }, this);
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.put = function(arg1, value, opt_keys) {

  var req;
  var me = this;

  if (arg1 instanceof ydn.db.Key) {
    /**
     * @type {!ydn.db.Key}
     */
    var k = arg1;
    var k_s_name = k.getStoreName();
    var k_store = this.schema.getStore(k_s_name);
    if (!k_store) {
      throw new ydn.debug.error.ArgumentException('store "' + k_s_name +
          '" not found.');
    }
    if (k_store.usedInlineKey()) {
      var v_k = k_store.extractKey(value);
      if (goog.isDefAndNotNull(v_k)) {
        if (ydn.db.cmp(v_k, k.getId()) != 0) {
          throw new ydn.debug.error.ArgumentException('Inline key must be ' +
              k + ' but ' + v_k + ' found.');
        }
      } else {
        k_store.setKeyValue(value, k.getId());
      }
      return this.put(k_s_name, value);
    } else {
      return this.put(k_s_name, value, k.getId());
    }
  } else if (goog.isArray(arg1)) { // array of keys
    if (goog.DEBUG && goog.isDef(opt_keys)) {
      throw new ydn.debug.error.ArgumentException('too many arguments');
    }
    var db_keys = /** @type {!Array.<!ydn.db.Key>} */ (arg1);
    if (goog.DEBUG && !goog.isDef(value)) {
      throw new ydn.debug.error.ArgumentException('record values required');
    }
    goog.asserts.assertArray(value, 'record values must also be in an array');
    var values = /** @type {!Array} */ (value);
    goog.asserts.assert(db_keys.length === values.length, 'number of keys ' +
        'and number of object must be same, but found ' + db_keys.length +
        ' vs. ' + values.length);
    var store_names = [];
    for (var i = 0, n = db_keys.length; i < n; i++) {
      var s_name = db_keys[i].getStoreName();
      if (goog.array.indexOf(store_names, s_name) == -1) {
        store_names.push(s_name);
      }
      var store = this.schema.getStore(s_name);
      if (!store) {
        throw new ydn.debug.error.ArgumentException('store "' + s_name +
            '" not found.');
      }
      if (store.usedInlineKey()) {
        store.setKeyValue(values[i], db_keys[i].getId());
      }
    }
    this.logger.finer('putByKeys: to ' + ydn.json.stringify(store_names) + ' ' +
        values.length + ' objects');

    for (var i = 0; i < values.length; i++) {
      store.generateIndex(values[i]);
    }
    req = this.tx_thread.request(ydn.db.Request.Method.PUT_KEYS, store_names,
        ydn.db.base.TransactionMode.READ_WRITE);
    store.hook(req, arguments);
    req.addTxback(function() {
      me.getCrudExecutor().putByKeys(req, values, db_keys);
    }, this);
  } else if (goog.isString(arg1) || goog.isObject(arg1)) {
    var store = this.getStore_(arg1);
    var st_name = store.getName();

    // https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore#put
    if (store.usedInlineKey() && goog.isDef(opt_keys)) {
      // The object store uses in-line keys or has a key generator, and a key
      // parameter was provided.
      throw new ydn.debug.error.ArgumentException(
          'key must not be provided while the store uses in-line key.');
    //} else if (store.autoIncrement && goog.isDef(opt_keys)) {
      // The object store uses in-line keys or has a key generator, and a key
      // parameter was provided.
    //  throw new ydn.debug.error.ArgumentException('key must not be provided' +
    //      ' while autoIncrement is true.');
    } else if (!store.usedInlineKey() && !store.autoIncrement &&
        !goog.isDef(opt_keys)) {
      // The object store uses out-of-line keys and has no key generator, and no
      // key parameter was provided.
      throw new ydn.debug.error.ArgumentException(
          'out-of-line key must be provided.');
    }

    if (goog.isArray(value)) {
      var objs = value;
      var keys = /** @type {!Array.<(number|string)>|undefined} */ (opt_keys);
      this.logger.finer('putObjects: ' + st_name + ' ' +
          objs.length + ' objects');
      for (var i = 0; i < objs.length; i++) {
        store.generateIndex(objs[i]);
      }
      req = this.tx_thread.request(ydn.db.Request.Method.PUTS,
          [st_name], ydn.db.base.TransactionMode.READ_WRITE);
      store.hook(req, arguments);
      req.addTxback(function() {
        //console.log('putObjects');
        this.getCrudExecutor().insertObjects(req, true, false, st_name, objs,
            keys);
      }, this);

      if (store.dispatch_events) {
        req.addCallback(function(keys) {
          var event = new ydn.db.events.StoreEvent(ydn.db.events.Types.UPDATED,
              this.getStorage(), st_name, keys, objs);
          this.getStorage().dispatchDbEvent(event);
        }, this);
      }

    } else if (goog.isObject(value)) {
      var obj = value;
      var key = /** @type {number|string|undefined} */ (opt_keys);
      if (goog.DEBUG) {
        if (goog.isDef(key)) {
          goog.asserts.assert(ydn.db.Key.isValidKey(key), key +
              ' of type ' + (typeof key) + ' is invalid key for ' +
              ydn.json.toShortString(obj));
        } else if (!store.isAutoIncrement() && store.usedInlineKey()) {
          goog.asserts.assert(ydn.db.Key.isValidKey(store.extractKey(obj)),
              'in-line key on ' + store.getKeyPath() + ' must provided in ' +
              ydn.json.toShortString(obj));
        }
      }
      this.logger.finer('putObject: ' + st_name + ' ' + key);
      // note File is also instanceof Blob
      var is_blob = (goog.isDef(goog.global['Blob']) && obj instanceof Blob) &&
          // check for using blob store
          store.isFixed() && !store.usedInlineKey() &&
          store.countIndex() == 0 &&
          // only webkit need to encode blob into dataURL.
          goog.userAgent.WEBKIT;
      if (is_blob) {
        // we cannot invoke request to thread, because encoding is async,
        // we must wait encoding is ready before starting transaction.
        // TODO: this will cause transaction could not be reused.
        req = new ydn.db.Request(ydn.db.Request.Method.PUT);
        var fr = new FileReader();
        fr.onload = function(e) {
          var value = e.target.result;
          var rq = me.tx_thread.request(ydn.db.Request.Method.PUT,
              [st_name], ydn.db.base.TransactionMode.READ_WRITE);
          store.hook(rq, [st_name, obj, key]);
          rq.addTxback(function() {
            me.getCrudExecutor().insertObjects(rq, true, true, st_name, [value],
                [key]);
          }, this);
          rq.addCallbacks(function(x) {
            req.callback(x);
          }, function(e) {
            req.errback(e);
          });
        };
        fr.onerror = function(e) {
          req.errback(e);
        };
        fr.onabort = function(e) {
          req.errback(e);
        };
        fr.readAsDataURL(/** @type {!Blob} */ (obj));
      } else {
        store.generateIndex(obj);
        req = this.tx_thread.request(ydn.db.Request.Method.PUT,
            [st_name], ydn.db.base.TransactionMode.READ_WRITE);
        var args = [st_name, obj, key];
        store.hook(req, args);
        req.addTxback(function() {
          // Note: here we are reading from arguments array, so that if
          // hook manipulate the value, we get updated value.
          // encryption hook manipulate both key and value.
          var keys = goog.isDef(key) ? [args[2]] : undefined;
          me.getCrudExecutor().insertObjects(req, true, true, st_name,
              [args[1]], keys);
        }, this);
      }


      if (store.dispatch_events) {
        req.addCallback(function(key) {
          var event = new ydn.db.events.RecordEvent(ydn.db.events.Types.UPDATED,
              this.getStorage(), st_name, key, obj);
          this.getStorage().dispatchDbEvent(event);
        }, this);
      }

    } else {
      throw new ydn.debug.error.ArgumentException('put record value must be ' +
          'Object or array of Objects');
    }
  } else {
    throw new ydn.debug.error.ArgumentException('the first argument of put ' +
        'must be store name, store schema or array of keys.');
  }

  return req;

};


/**
 * Dump object into the database. Use only by synchronization process when
 * updating from server.
 * This is friendly module use only.
 * @param {string|!Array.<!ydn.db.Key>} store_name store name.
 * @param {!Array.<Object>} objs objects.
 * @param {!Array.<!IDBKey>=} opt_keys keys.
 * @param {boolean=} opt_use_main_thread default is background thread.
 * @param {number=} opt_hook_idx hook index to ignore.
 * @return {!goog.async.Deferred} df return no result.
 */
ydn.db.crud.DbOperator.prototype.dumpInternal = function(store_name, objs,
    opt_keys, opt_use_main_thread, opt_hook_idx) {
  var thread = opt_use_main_thread ? this.tx_thread : this.sync_thread;

  var store_names, db_keys;
  if (goog.isString(store_name)) {
    var store = this.schema.getStore(store_name);
    if (goog.DEBUG) {
      if (store) {
        if (!store.usedInlineKey() && !store.isAutoIncrement() &&
            !goog.isDefAndNotNull(opt_keys)) {
          throw new ydn.debug.error.ArgumentException(
              'key required for store "' + store_name + '"');
        }
      } else {
        throw new ydn.db.NotFoundError(store_name);
      }
    }
    for (var i = 0; i < objs.length; i++) {
      store.generateIndex(objs[i]);
    }
    store_names = [store_name];
  } else {
    goog.asserts.assertArray(store_name, 'store name ' + store_name + ' +' +
        ' must be an array or string, but ' + (typeof store_name));
    db_keys = store_name;
    store_names = [];
    for (var i = 0, n = db_keys.length; i < n; i++) {
      var s_name = db_keys[i].getStoreName();
      var store = this.schema.getStore(s_name);
      if (goog.array.indexOf(store_names, s_name) == -1) {
        store_names.push(s_name);
      }
      if (goog.DEBUG && !store) {
        throw new ydn.db.NotFoundError(s_name);
      }
      store.generateIndex(objs[i]);
    }
  }

  var req;
  if (goog.isString(store_name)) {
    var s_n = store_name;
    var store = this.schema.getStore(s_n);
    req = thread.request(ydn.db.Request.Method.PUTS,
        store_names, ydn.db.base.TransactionMode.READ_WRITE);
    if (opt_hook_idx) {
      store.hook(req, [s_n, objs, opt_keys], opt_hook_idx);
    }
    req.addTxback(function() {
      this.getCrudExecutor().insertObjects(req, true, false, s_n, objs,
          opt_keys);
    }, this);
  } else {
    req = thread.request(ydn.db.Request.Method.PUT_KEYS,
        store_names, ydn.db.base.TransactionMode.READ_WRITE);
    if (opt_hook_idx) {
      for (var i = 0; i < store_names.length; i++) {
        var store = this.schema.getStore(store_names[i]);
        store.hook(req, [objs, db_keys], opt_hook_idx);
      }
    }
    req.addTxback(function() {
      this.getCrudExecutor().putByKeys(req, objs, db_keys);
    }, this);
  }
  return req;
};


/**
 * Remove record by keys.
 * @param {!Array.<!ydn.db.Key>} keys keys.
 * @return {!ydn.db.Request} df.
 */
ydn.db.crud.DbOperator.prototype.removeInternalByKeys = function(keys) {
  var store_names = [];
  for (var i = 0, n = keys.length; i < n; i++) {
    var s_name = keys[i].getStoreName();
    if (goog.array.indexOf(store_names, s_name) == -1) {
      store_names.push(s_name);
    }
    if (goog.DEBUG && !this.schema.hasStore(s_name)) {
      throw new ydn.db.NotFoundError(s_name);
    }
  }
  var me = this;
  var df = this.sync_thread.request(ydn.db.Request.Method.REMOVE_KEYS,
      store_names, ydn.db.base.TransactionMode.READ_WRITE);
  df.addTxback(function() {
    this.getCrudExecutor().removeByKeys(df, keys);
  }, this);
  return df;
};


/**
 * Remove record by keys.
 * @param {string} store_name store_name.
 * @param {IDBKeyRange=} opt_kr key range.
 * @return {!ydn.db.Request} df.
 */
ydn.db.crud.DbOperator.prototype.removeInternal = function(store_name, opt_kr) {

  var df = this.sync_thread.request(ydn.db.Request.Method.REMOVE, [store_name],
      ydn.db.base.TransactionMode.READ_WRITE);
  df.addTxback(function() {
    this.getCrudExecutor().removeByKeyRange(df, store_name, opt_kr || null);
  }, this);
  return df;
};


/**
 * List records from the database. Use only by synchronization process when
 * updating from server.
 * This is friendly module use only.
 * @param {string} store_name store name.
 * @param {?string} index_name index name.
 * @param {IDBKeyRange|ydn.db.KeyRange} key_range key range.
 * @param {boolean} reverse reverse.
 * @param {number} limit limit.
 * @param {number=} opt_offset offset.
 * @return {!goog.async.Deferred} df.
 */
ydn.db.crud.DbOperator.prototype.listInternal = function(store_name, index_name,
    key_range, reverse, limit, opt_offset) {
  limit = limit || ydn.db.base.DEFAULT_RESULT_LIMIT;
  var req;
  var offset = opt_offset || 0;
  if (goog.DEBUG) {
    var store = this.schema.getStore(store_name);
    if (store) {
      if (index_name && !store.hasIndex(index_name)) {
        throw new ydn.db.NotFoundError('index "' + index_name + '" in store "' +
            store_name + '"');
      }
    } else {
      throw new ydn.db.NotFoundError(store_name);
    }
  }

  var kr = ydn.db.KeyRange.parseIDBKeyRange(key_range);
  // todo: unify if
  if (goog.isString(index_name)) {
    var index = index_name;
    req = this.sync_thread.request(ydn.db.Request.Method.VALUES_INDEX,
        [store_name]);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_VALUE,
          store_name, index, kr, limit, offset, reverse, false);
    }, this);
  } else {
    req = this.sync_thread.request(ydn.db.Request.Method.VALUES,
        [store_name]);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_VALUE,
          store_name, null, kr, limit, offset, reverse, false);
    }, this);
  }
  return req;
};


/**
 * Retrieve record values from given list of key objects.
 * @param {!Array.<!ydn.db.Key>} keys keys to retrieve.
 * @return {!ydn.db.Request} df.
 */
ydn.db.crud.DbOperator.prototype.valuesInternal = function(keys) {
  var store_names = [];
  var n = keys.length;
  if (n == 0) {
    return ydn.db.Request.succeed(ydn.db.Request.Method.KEYS, []);
  }
  for (var i = 0; i < n; i++) {
    var s_name = keys[i].getStoreName();
    if (goog.array.indexOf(store_names, s_name) == -1) {
      store_names.push(s_name);
    }
    if (goog.DEBUG && !this.schema.hasStore(s_name)) {
      throw new ydn.db.NotFoundError(s_name);
    }
  }
  var me = this;
  var df = this.sync_thread.request(ydn.db.Request.Method.KEYS, store_names);
  df.addTxback(function() {
    me.getCrudExecutor().listByKeys(df, keys);
  }, this);
  return df;
};


/**
 * Count number of records in stores.
 * @param {!Array.<string>} store_names
 * @param {boolean=} opt_use_main_thread default is background thread.
 * @return {!ydn.db.Request}
 */
ydn.db.crud.DbOperator.prototype.countInternal = function(store_names,
                                                          opt_use_main_thread) {
  var thread = opt_use_main_thread ? this.tx_thread : this.sync_thread;
  var req = thread.request(ydn.db.Request.Method.COUNT,
      store_names);
  req.addTxback(function() {
    this.getCrudExecutor().countStores(req, store_names);
  }, this);
  return req;
};


/**
 * List keys from the database. Use only by synchronization process when
 * updating from server.
 * This is friendly module use only.
 * @param {string} store_name store name.
 * @param {?string} index_name index name.
 * @param {?IDBKeyRange} key_range key range.
 * @param {number} limit limit.
 * @param {number} offset limit.
 * @param {boolean} reverse reverse.
 * @param {boolean} unique limit.
 * @return {!ydn.db.Request} df.
 */
ydn.db.crud.DbOperator.prototype.keysInternal = function(store_name, index_name,
    key_range, limit, offset, reverse, unique) {
  var req;
  var me = this;
  limit = limit || ydn.db.base.DEFAULT_RESULT_LIMIT;

  if (goog.DEBUG) {
    var store = this.schema.getStore(store_name);
    if (store) {
      if (index_name && !store.hasIndex(index_name)) {
        throw new ydn.db.NotFoundError('index "' + index_name + '" in store "' +
            store_name + '"');
      }
    } else {
      throw new ydn.db.NotFoundError(store_name);
    }
  }

  if (goog.isString(index_name)) {
    var index = index_name;
    req = this.sync_thread.request(ydn.db.Request.Method.KEYS_INDEX,
        [store_name]);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_PRIMARY_KEY,
          store_name, index, key_range, limit, offset, reverse, unique);
    }, this);
  } else {
    req = this.sync_thread.request(ydn.db.Request.Method.KEYS,
        [store_name]);
    req.addTxback(function() {
      this.getCrudExecutor().list(req, ydn.db.base.QueryMethod.LIST_PRIMARY_KEY,
          store_name, null, key_range, limit, offset, reverse, unique);
    }, this);
  }
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.clear = function(arg1, arg2, arg3) {

  if (goog.DEBUG && goog.isDef(arg3)) {
    throw new ydn.debug.error.ArgumentException('too many input arguments');
  }

  var req;
  var me = this;

  if (goog.isString(arg1)) {
    var st_name = arg1;
    var store = this.schema.getStore(st_name);
    if (!store) {
      throw new ydn.debug.error.ArgumentException('store name "' + st_name +
          '" not found.');
    }

    if (goog.isObject(arg2)) {
      var key_range = ydn.db.KeyRange.parseIDBKeyRange(
          /** @type {KeyRangeJson} */ (arg2));
      if (goog.isNull(key_range)) {
        throw new ydn.debug.error.ArgumentException('clear method requires' +
            ' a valid non-null KeyRange object.');
      }
      this.logger.finer('clearByKeyRange: ' + st_name + ':' +
          ydn.json.stringify(key_range));
      req = this.tx_thread.request(ydn.db.Request.Method.CLEAR, [st_name],
          ydn.db.base.TransactionMode.READ_WRITE);
      store.hook(req, [st_name, key_range]);
      req.addTxback(function() {
        this.getCrudExecutor().clearByKeyRange(req, st_name, key_range);
      }, this);
    } else if (!goog.isDef(arg2)) {
      this.logger.finer('clearByStore: ' + st_name);
      req = this.tx_thread.request(ydn.db.Request.Method.CLEAR, [st_name],
          ydn.db.base.TransactionMode.READ_WRITE);
      req.addTxback(function() {
        this.getCrudExecutor().clearByStores(req, [st_name]);
      }, this);

    } else {
      throw new ydn.debug.error.ArgumentException('clear method requires' +
          ' a valid KeyRange object as second argument, but found ' + arg2 +
          ' of type ' + typeof arg2);
    }

  } else if (!goog.isDef(arg1) || goog.isArray(arg1) &&
      goog.isString(arg1[0])) {
    var store_names = arg1 || this.schema.getStoreNames();
    this.logger.finer('clearByStores: ' + ydn.json.stringify(store_names));
    req = this.tx_thread.request(ydn.db.Request.Method.CLEAR, store_names,
        ydn.db.base.TransactionMode.READ_WRITE);
    req.addTxback(function() {
      this.getCrudExecutor().clearByStores(req, store_names);
    }, this);

  } else {
    throw new ydn.debug.error.ArgumentException('first argument "' + arg1 +
        '" is invalid.');
  }

  return req;
};


/**
 * @inheritDoc
 */
ydn.db.crud.DbOperator.prototype.remove = function(arg1, arg2, arg3) {

  var req;

  if (goog.isString(arg1)) {
    /**
     * @type {string}
     */
    var store_name = arg1;
    var store = this.schema.getStore(store_name);
    if (!store) {
      throw new ydn.debug.error.ArgumentException('store name "' + store_name +
          '" not found.');
    }
    if (goog.isDef(arg3)) {
      if (goog.isString(arg2)) {
        var index = store.getIndex(arg2);
        if (!index) {
          throw new ydn.debug.error.ArgumentException('index: ' + arg2 +
              ' not found in ' + store_name);
        }
        if (goog.isObject(arg3) || goog.isNull(arg3)) {
          var key_range = ydn.db.KeyRange.parseIDBKeyRange(
              /** @type {KeyRangeJson} */ (arg3));
          this.logger.finer('removeByIndexKeyRange: ' + store_name + ':' +
              index.getName() + ' ' + store_name);
          req = this.tx_thread.request(ydn.db.Request.Method.REMOVE_INDEX,
              [store_name], ydn.db.base.TransactionMode.READ_WRITE);
          req.addTxback(function() {
            this.getCrudExecutor().removeByIndexKeyRange(req, store_name,
                index.getName(), key_range);
          }, this);
        } else {
          throw new ydn.debug.error.ArgumentException('key range ' + arg3 +
              ' is invalid type "' + typeof arg3 + '".');
        }
      } else {
        throw new ydn.debug.error.ArgumentException('index name "' + arg2 +
            '" must be a string, but ' + typeof arg2 + ' found.');
      }
    } else {
      if (goog.isString(arg2) || goog.isNumber(arg2) ||
          goog.isArrayLike(arg2) || arg2 instanceof Date) {
        var id = /** @type {IDBKey} */ (arg2);
        this.logger.finer('removeById: ' + store_name + ':' + id);
        req = this.tx_thread.request(ydn.db.Request.Method.REMOVE_ID,
            [store_name], ydn.db.base.TransactionMode.READ_WRITE);
        var rm_args = [store_name, id];
        store.hook(req, rm_args);
        req.addTxback(function() {
          this.getCrudExecutor().removeById(req, store_name, rm_args[1]);
        }, this);

        if (store.dispatch_events) {
          req.addCallback(function(key) {
            var event = new ydn.db.events.RecordEvent(
                ydn.db.events.Types.DELETED,
                this.getStorage(), store_name, key, undefined);
            this.getStorage().dispatchDbEvent(event);
          }, this);
        }

      } else if (goog.isObject(arg2)) {
        var key_range = ydn.db.KeyRange.parseIDBKeyRange(
            /** @type {KeyRangeJson} */ (arg2));
        this.logger.finer('removeByKeyRange: ' + store_name + ':' +
            ydn.json.stringify(key_range));
        req = this.tx_thread.request(ydn.db.Request.Method.REMOVE,
            [store_name], ydn.db.base.TransactionMode.READ_WRITE);
        store.hook(req, [store_name, key_range]);
        req.addTxback(function() {
          this.getCrudExecutor().removeByKeyRange(req, store_name, key_range);
        }, this);
        if (store.dispatch_events) {
          req.addCallback(function(key) {
            var event = new ydn.db.events.StoreEvent(
                ydn.db.events.Types.DELETED,
                this.getStorage(), store_name, key, undefined);
            this.getStorage().dispatchDbEvent(event);
          }, this);
        }
      } else {
        throw new ydn.debug.error.ArgumentException(
            'Invalid key or key range "' + arg2 + '" of type ' + typeof arg2);
      }
    }
  } else if (arg1 instanceof ydn.db.Key) {
    /**
     * @type {!ydn.db.Key}
     */
    var key = arg1;
    var st_name = key.getStoreName();
    var store = this.schema.getStore(st_name);
    goog.asserts.assert(store, 'store "' + st_name + '" not found.');
    req = this.tx_thread.request(ydn.db.Request.Method.REMOVE_ID,
        [st_name], ydn.db.base.TransactionMode.READ_WRITE);
    var hk_args = [st_name, key.getId()];
    store.hook(req, hk_args);
    req.addTxback(function() {
      this.getCrudExecutor().removeById(req, st_name, hk_args[1]);
    }, this);
  } else if (goog.isArray(arg1)) {
    /**
     * @type {!Array.<!ydn.db.Key>}
     */
    var arr = arg1;
    var store_names = [];
    for (var i = 0, n = arr.length; i < n; i++) {
      if (goog.DEBUG && !(arr[i] instanceof ydn.db.Key)) {
        throw new ydn.debug.error.ArgumentException('key list element at ' + i +
            ' of ' + n + ' must be yn.db.Key, but "' +
            ydn.json.toShortString(arg1[i]) +
            '" (' + goog.typeOf(arg1[i]) + ') ' +
            'is not ydn.db.Key.');
      }
      var st = arr[i].getStoreName();
      if (goog.array.indexOf(store_names, st) == -1) {
        store_names.push(st);
      }
    }
    if (store_names.length < 1) {
      throw new ydn.debug.error.ArgumentException('at least one valid key ' +
          'required in key list "' + ydn.json.toShortString(arg1) + '"');
    }
    req = this.tx_thread.request(ydn.db.Request.Method.REMOVE_KEYS,
        store_names, ydn.db.base.TransactionMode.READ_WRITE);
    req.addTxback(function() {
      this.getCrudExecutor().removeByKeys(req, arr);
    }, this);
  } else {
    throw new ydn.debug.error.ArgumentException('first argument requires ' +
        'store name, key (ydn.db.Key) or list of keys (array) , but "' +
        ydn.json.toShortString(arg1) + '" (' + goog.typeOf(arg1) + ') found.');
  }

  return req;
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.crud.DbOperator.prototype.toString = function() {
    var s = 'DbOperator:' + this.getStorage().getName();
    return s;
  };
}


// Input 52
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Database operator providing index and table scan query.
*
* @author kyawtun@yathit.com (Kyaw Tun)
*/

goog.provide('ydn.db.core.DbOperator');
goog.require('ydn.db.Iterator');
goog.require('ydn.db.algo.AbstractSolver');
goog.require('ydn.db.core.IOperator');
goog.require('ydn.db.core.req.IRequestExecutor');
goog.require('ydn.db.crud.DbOperator');
goog.require('ydn.debug.error.ArgumentException');



/**
 * Construct storage to execute CRUD database operations.
 *
 * Execution database operation is atomic, if a new transaction require,
 * otherwise existing transaction is used and the operation become part of
 * the existing transaction. A new transaction is required if the transaction
 * is not active or locked. Active transaction can be locked by using
 * mutex.
 *
 * @param {!ydn.db.crud.Storage} storage base storage object.
 * @param {!ydn.db.schema.Database} schema schema.
 * @param {ydn.db.tr.Thread} thread execution thread.
 * @param {ydn.db.tr.Thread} sync_thread synchronization thread.
 * @implements {ydn.db.core.IOperator}
 * @constructor
 * @extends {ydn.db.crud.DbOperator}
 * @struct
*/
ydn.db.core.DbOperator = function(storage, schema, thread,
                                  sync_thread) {
  goog.base(this, storage, schema, thread, sync_thread);
};
goog.inherits(ydn.db.core.DbOperator, ydn.db.crud.DbOperator);


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.DbOperator.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.DbOperator');


/**
 * @define {boolean} debug flag.
 */
ydn.db.core.DbOperator.DEBUG = false;


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.get = function(arg1, arg2) {

  var me = this;
  if (arg1 instanceof ydn.db.Iterator) {
    /**
     * @type {!ydn.db.Iterator}
     */
    var q = arg1;
    var q_store_name = q.getStoreName();
    var store = this.schema.getStore(q_store_name);
    if (!store) {
      throw new ydn.debug.error.ArgumentException('store "' +
          q_store_name + '" not found.');
    }
    var index_name = q.getIndexName();
    if (goog.isDef(index_name) && !store.hasIndex(index_name)) {
      throw new ydn.debug.error.ArgumentException('index "' +
          index_name + '" not found in store "' + q_store_name + '".');
    }
    this.logger.finer('getByIterator:' + q);
    var df = this.tx_thread.request(ydn.db.Request.Method.GET_ITER,
        [q_store_name]);
    df.addTxback(function() {
      this.iterate(ydn.db.base.QueryMethod.GET, df, q, 1);
    }, this);
    return df;
  } else {
    return goog.base(this, 'get', arg1, arg2);
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.keys = function(arg1, arg2, arg3, arg4, arg5,
                                                 arg6, arg7) {

  var me = this;
  if (arg1 instanceof ydn.db.Iterator) {

    /**
     * @type {number}
     */
    var limit = ydn.db.base.DEFAULT_RESULT_LIMIT;
    if (goog.isNumber(arg2)) {
      limit = /** @type {number} */ (arg2);
      if (limit < 1) {
        throw new ydn.debug.error.ArgumentException('limit must be ' +
            'a positive value, but ' + arg2);
      }
    } else if (goog.isDef(arg2)) {
      throw new ydn.debug.error.ArgumentException('limit must be a number, ' +
          ' but ' + arg2);
    }
    if (goog.isDef(arg3)) {
      throw new ydn.debug.error.ArgumentException(
          'offset must not be specified');
    }

    /**
     *
     * @type {!ydn.db.Iterator}
     */
    var q = arg1;

    this.logger.finer('keysByIterator:' + q);
    var df = this.tx_thread.request(ydn.db.Request.Method.KEYS_ITER,
        q.stores());
    df.addTxback(function() {
      if (q.isIndexIterator()) {
        this.iterate(ydn.db.base.QueryMethod.LIST_KEY, df, q, limit);
      } else {
        this.iterate(ydn.db.base.QueryMethod.LIST_PRIMARY_KEY, df, q, limit);
      }
    }, this);

    return df;
  } else {
    return goog.base(this, 'keys', arg1, arg2, arg3, arg4, arg5, arg6, arg7);
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.count = function(arg1, arg2, arg3) {

  var me = this;
  if (arg1 instanceof ydn.db.Iterator) {
    if (goog.isDef(arg2) || goog.isDef(arg3)) {
      throw new ydn.debug.error.ArgumentException('too many arguments.');
    }

    /**
     *
     * @type {!ydn.db.Iterator}
     */
    var q = arg1;
    this.logger.finer('countIterator:' + q);
    var df = this.tx_thread.request(ydn.db.Request.Method.COUNT, q.stores());
    df.addTxback(function() {
      this.iterate(ydn.db.base.QueryMethod.COUNT, df, q);
    }, this);

    return df;
  } else {
    return goog.base(this, 'count', arg1, arg2, arg3);
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.values = function(arg1, arg2, arg3, arg4,
                                                   arg5, arg6) {

  var me = this;
  if (arg1 instanceof ydn.db.Iterator) {

    /**
     * @type {number}
     */
    var limit;
    if (goog.isNumber(arg2)) {
      limit = /** @type {number} */ (arg2);
      if (limit < 1) {
        throw new ydn.debug.error.ArgumentException('limit must be ' +
            'a positive value, but ' + limit);
      }
    } else if (goog.isDef(arg2)) {
      throw new ydn.debug.error.ArgumentException('limit must be a number, ' +
          'but ' + arg2);
    }
    if (goog.isDef(arg3)) {
      throw new ydn.debug.error.ArgumentException(
          'offset must not be specified');
    }

    /**
     *
     * @type {!ydn.db.Iterator}
     */
    var q = arg1;
    this.logger.finer('listByIterator:' + q);
    var df = this.tx_thread.request(ydn.db.Request.Method.VALUES_ITER,
        q.stores());
    df.addTxback(function() {
      if (q.isKeyIterator()) {
        this.iterate(ydn.db.base.QueryMethod.LIST_PRIMARY_KEY, df, q, limit);
      } else {
        this.iterate(ydn.db.base.QueryMethod.LIST_VALUE, df, q, limit);
      }
    }, this);

    return df;
  } else {
    return goog.base(this, 'values', arg1, arg2, arg3, arg4, arg5, arg6);
  }

};


/**
 * Cursor scan iteration.
 * @param {!ydn.db.algo.AbstractSolver|function(!Array, !Array): !Array} solver
 * solver.
 * @param {!Array.<!ydn.db.Iterator>=} opt_iterators the cursor.
 * @return {!goog.async.Deferred} promise on completed.
 */
ydn.db.core.DbOperator.prototype.scan = function(solver, opt_iterators) {

  if (goog.DEBUG) {
    if (goog.isDef(opt_iterators)) {
      if (!goog.isArray(opt_iterators)) {
        throw new ydn.debug.error.ArgumentException('Iterator argument must' +
            ' be an array.');
      }
      for (var i = 0; i < opt_iterators.length; i++) {
        var is_iter = opt_iterators[i] instanceof ydn.db.Iterator;
        if (!is_iter) {
          throw new ydn.debug.error.ArgumentException('Iterator at ' + i +
              ' must be cursor range iterator.');
        }
      }
    }
  }

  /**
   * @type {!Array.<!ydn.db.Iterator>}
   */
  var iterators;
  if (opt_iterators) {
    iterators = opt_iterators;
  } else {
    var iter = solver.getIterators();
    goog.asserts.assertArray(iter, 'array of iterators required.');
    iterators = iter;
  }

  var tr_mode = ydn.db.base.TransactionMode.READ_ONLY;

  var scopes = [];
  for (var i = 0; i < iterators.length; i++) {
    var stores = iterators[i].stores();
    for (var j = 0; j < stores.length; j++) {
      if (!goog.array.contains(scopes, stores[j])) {
        scopes.push(stores[j]);
      }
    }
  }

  this.logger.finest(this + ': scan for ' + iterators.length +
      ' iterators on ' + scopes);

  var me = this;
  var df = this.tx_thread.request(ydn.db.Request.Method.SCAN, scopes);

  this.tx_thread.exec(df, function(tx, tx_no, cb) {

    var lbl = tx_no + ' ' + me + ' scanning';
    me.logger.finest(lbl);
    var done = false;

    var total;
    var idx2iterator = []; // convert main index to iterator index

    var keys = [];
    var values = [];
    /**
     *
     * @type {Array.<!ydn.db.core.req.ICursor>}
     */
    var cursors = [];

    var do_exit = function() {

      for (var k = 0; k < cursors.length; k++) {
        cursors[k].exit();
      }
      done = true;
      goog.array.clear(cursors);
      // console.log('existing');
      me.logger.finer('success ' + lbl);
      cb(undefined);
    };

    var result_count = 0;
    var streamer_result_count = 0;
    var has_key_count = 0;

    /**
     * All results collected. Now invoke solver and do advancement.
     */
    var on_result_ready = function() {

      // all cursor has results, than sent to join algorithm callback.

      var out;
      if (solver instanceof ydn.db.algo.AbstractSolver) {
        out = solver.solver(keys, values);
      } else {
        out = solver(keys, values);
      }
      if (ydn.db.core.DbOperator.DEBUG) {
        window.console.log(me + ' received result from solver ' +
            ydn.json.stringify(out) + ' for keys ' + ydn.json.stringify(keys));
      }
      var next_primary_keys = [];
      var next_effective_keys = [];
      var advance = [];
      var restart = [];
      if (goog.isArray(out)) {
        // adv vector is given
        for (var i = 0; i < out.length; i++) {
          if (out[i] === true) {
            advance[i] = 1;
          } else if (out[i] === false) {
            restart[i] = true;
          } else {
            next_effective_keys[i] = out[i];
          }
        }
      } else if (goog.isNull(out)) {
        // all stop
        next_primary_keys = [];
      } else if (!goog.isDef(out)) {
        // all continue;
        next_primary_keys = [];
        for (var i = 0; i < iterators.length; i++) {
          if (goog.isDef(idx2iterator[i])) {
            advance[i] = 1;
          }
        }
      } else if (goog.isObject(out)) {
        if (goog.DEBUG) {
          var valid_att = ['advance', 'continue', 'continuePrimary', 'restart'];
          for (var key in out) {
            if (!goog.array.contains(valid_att, key)) {
              throw new ydn.debug.error.InvalidOperationException(
                  'Unknown attribute "' + key +
                  '" in cursor advancement object');
            }
          }
        }
        next_primary_keys = out['continuePrimary'] || [];
        next_effective_keys = out['continue'] || [];
        advance = out['advance'] || [];
        restart = out['restart'] || [];
      } else {
        throw new ydn.debug.error.InvalidOperationException(
            'scan callback output');
      }
      var move_count = 0;
      result_count = 0;
      for (var i = 0; i < iterators.length; i++) {
        if (goog.isDefAndNotNull(next_primary_keys[i]) ||
            goog.isDef(next_effective_keys[i]) ||
            goog.isDefAndNotNull(restart[i]) ||
            goog.isDefAndNotNull(advance[i])) {
          // by marking non moving iterator first, both async and sync callback
          // work.
        } else {
          // take non advancing iterator as already moved.
          result_count++;
        }
      }
      for (var i = 0; i < iterators.length; i++) {
        if (goog.isDefAndNotNull(next_primary_keys[i]) ||
            goog.isDef(next_effective_keys[i]) ||
            goog.isDefAndNotNull(restart[i]) ||
            goog.isDefAndNotNull(advance[i])) {
          var idx = idx2iterator[i];
          if (!goog.isDef(idx)) {
            throw new ydn.error.InvalidOperationException(i +
                ' is not an iterator.');
          }
          var iterator = iterators[idx];
          var cursor = cursors[i];
          if (goog.DEBUG && !goog.isDefAndNotNull(keys[i])) {
            var at = i + '/' + iterators.length;
            if (goog.isDefAndNotNull(advance[i])) {
              throw new ydn.error.InvalidOperationError(cursor + ' ' + at +
                  ' must not advance ' + advance[i] + ' steps');
            } else if (goog.isDef(next_effective_keys[i])) {
              throw new ydn.error.InvalidOperationError(cursor + ' ' + at +
                  ' must not continue to key ' + next_effective_keys[i]);
            } else if (goog.isDefAndNotNull(next_primary_keys[i])) {
              throw new ydn.error.InvalidOperationError(cursor + ' ' + at +
                  ' must not continue to primary key ' + next_primary_keys[i]);
            }
          }

          keys[i] = undefined;
          values[i] = undefined;

          if (goog.isDefAndNotNull(restart[i])) {
            if (ydn.db.core.DbOperator.DEBUG) {
              window.console.log('cursor ' + cursor + ' of iterator ' +
                  iterator + ': restarting.');
            }
            goog.asserts.assert(restart[i] === true, i +
                ' restart must be true');
            cursor.restart();
          } else if (goog.isDef(next_effective_keys[i])) {
            if (ydn.db.core.DbOperator.DEBUG) {
              window.console.log(iterator + ': continuing to ' +
                  next_effective_keys[i]);
            }
            cursor.continueEffectiveKey(next_effective_keys[i]);
          } else if (goog.isDefAndNotNull(next_primary_keys[i])) {
            if (ydn.db.core.DbOperator.DEBUG) {
              window.console.log(cursor + ': continuing to primary key ' +
                  next_primary_keys[i]);
            }
            cursor.continuePrimaryKey(next_primary_keys[i]);
          } else if (goog.isDefAndNotNull(advance[i])) {
            if (ydn.db.core.DbOperator.DEBUG) {
              window.console.log(iterator + ': advancing ' + advance[i] +
                  ' steps.');
            }
            goog.asserts.assert(advance[i] === 1, i +
                ' advance value must be 1');

            cursor.advance(1);
          } else {
            throw new ydn.error.InternalError(iterator + ': has no action');
          }
          move_count++;
        }
      }
      // console.log(['on_result_ready', move_count, keys, adv]);
      if (move_count == 0) {
        do_exit();
      }

    };

    /**
     * Received iterator result. When all iterators result are collected,
     * begin to send request to collect streamers results.
     * @param {number} i index.
     * @param {IDBKey=} opt_key effective key.
     */
    var on_iterator_next = function(i, opt_key) {
      if (done) {
        if (ydn.db.core.DbOperator.DEBUG) {
          window.console.log('iterator ' + i + ' done');
        }
        // calling next to a terminated iterator
        throw new ydn.error.InternalError();
      }
      result_count++;
      var is_result_ready = result_count === total;
      var idx = idx2iterator[i];
      /**
       * @type {!ydn.db.Iterator}
       */
      var iterator = iterators[idx];
      /**
       * @type {!ydn.db.core.req.ICursor}
       */
      var cursor = cursors[idx];
      var primary_key = cursor.getPrimaryKey();
      var value = cursor.getValue();
      if (ydn.db.core.DbOperator.DEBUG) {
        var key_str = opt_key +
            (goog.isDefAndNotNull(primary_key) ? ', ' + primary_key : '');
        var ready_str = is_result_ready ? ' (all result done)' : '';
        window.console.log(cursor + ' new position ' + key_str + ready_str);
      }

      keys[i] = opt_key;
      if (iterator.isIndexIterator()) {
        if (iterator.isKeyIterator()) {
          values[i] = primary_key;
        } else {
          values[i] = value;
        }
      } else {
        if (iterator.isKeyIterator()) {
          values[i] = opt_key;
        } else {
          values[i] = value;
        }
      }

      if (is_result_ready) { // receive all results
        on_result_ready();
      }

    };

    var on_error = function(e) {
      for (var k = 0; k < cursors.length; k++) {
        cursors[k].exit();
      }
      goog.array.clear(cursors);
      me.logger.finer(lbl + ' error');
      cb(e, true);
    };

    var open_iterators = function() {
      var idx = 0;
      for (var i = 0; i < iterators.length; i++) {
        var iterator = iterators[i];
        var names = iterator.stores();
        var crs = [];
        for (var ni = 0; ni < names.length; ni++) {
          crs[ni] = me.getIndexExecutor().getCursor(tx, tx_no, names[ni]);
        }
        var cursor = iterator.load(crs);
        cursor.onFail = on_error;
        cursor.onNext = goog.partial(on_iterator_next, idx);
        cursors[i] = cursor;
        idx2iterator[idx] = i;
        idx++;
      }

      total = iterators.length;
    };

    if (solver instanceof ydn.db.algo.AbstractSolver) {
      var wait = solver.begin(iterators, function() {
        open_iterators();
      });
      if (!wait) {
        open_iterators();
      }
    } else {
      open_iterators();
    }

  }, scopes, tr_mode);

  return df;
};


/**
 * @return {ydn.db.core.req.IRequestExecutor} executor.
 */
ydn.db.core.DbOperator.prototype.getIndexExecutor = function() {
  return /** @type {ydn.db.core.req.IRequestExecutor} */ (this.getExecutor());
};


/**
 *
 * @param {function(this: T, !ydn.db.core.req.ICursor)} callback icursor
 * handler.
 * @param {!ydn.db.Iterator} iter the cursor.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode.
 * @param {T=} opt_scope optional callback scope.
 * @return {!ydn.db.Request} promise on completed.
 * @template T
 */
ydn.db.core.DbOperator.prototype.open = function(callback, iter, opt_mode,
                                                 opt_scope) {
  if (goog.DEBUG && !(iter instanceof ydn.db.Iterator)) {
    throw new ydn.debug.error.ArgumentException(
        'Second argument must be cursor range iterator.');
  }
  var store = this.schema.getStore(iter.getStoreName());
  if (!store) {
    throw new ydn.debug.error.ArgumentException('Store "' +
        iter.getStoreName() + '" not found.');
  }
  var tr_mode = opt_mode || ydn.db.base.TransactionMode.READ_ONLY;

  var me = this;
  var df = this.tx_thread.request(ydn.db.Request.Method.OPEN, iter.stores(),
      tr_mode);
  this.logger.finer('open:' + tr_mode + ' ' + iter);
  df.addTxback(function(tx) {
    var tx_no = df.getLabel();
    var lbl = tx_no + ' iterating ' + iter;
    me.logger.finer(lbl);

    var names = iter.stores();
    var crs = [];
    for (var ni = 0; ni < names.length; ni++) {
      crs[ni] = me.getIndexExecutor().getCursor(tx, tx_no, names[ni]);
    }
    var cursor = iter.load(crs);

    cursor.onFail = function(e) {
      df.setDbValue(e, true);
    };
    /**
     * callback.
     * @param {IDBKey=} opt_key effective key.
     */
    cursor.onNext = function(opt_key) {
      if (goog.isDefAndNotNull(opt_key)) {
        var adv = callback.call(opt_scope, cursor);
        if (adv === true) {
          cursor.restart();
        } else if (goog.isObject(adv)) {
          if (adv['restart'] === true) {
            cursor.restart(adv['continue'], adv['continuePrimary']);
          } else if (goog.isDefAndNotNull(adv['continue'])) {
            cursor.continueEffectiveKey(adv['continue']);
          } else if (goog.isDefAndNotNull(adv['continuePrimary'])) {
            cursor.continuePrimaryKey(adv['continuePrimary']);
          } else {
            cursor.exit();
            df.setDbValue(undefined); // break the loop
          }
        } else {
          cursor.advance(1);
        }
      } else {
        cursor.exit();
        df.setDbValue(undefined);
      }
    };

  }, this);

  return df;

};


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.map = function(iterator, callback) {

  var me = this;
  var stores = iterator.stores();
  for (var store, i = 0; store = stores[i]; i++) {
    if (!store) {
      throw new ydn.debug.error.ArgumentException('Store "' + store +
          '" not found.');
    }
  }
  var df = this.tx_thread.request(ydn.db.Request.Method.MAP, stores);
  this.logger.finest('map:' + iterator);
  this.tx_thread.exec(df, function(tx, tx_no, cb) {

    var lbl = tx_no + ' iterating ' + iterator;
    me.logger.finest(lbl);

    var names = iterator.stores();
    var crs = [];
    for (var ni = 0; ni < names.length; ni++) {
      crs[ni] = me.getIndexExecutor().getCursor(tx, tx_no, names[ni]);
    }
    var cursor = iterator.load(crs);

    cursor.onFail = function(e) {
      cb(e, false);
    };
    /**
     *
     * @param {IDBKey=} opt_key effective key.
     */
    cursor.onNext = function(opt_key) {
      if (goog.isDefAndNotNull(opt_key)) {
        var key = opt_key;
        var ref;
        if (iterator.isIndexIterator()) {
          if (iterator.isKeyIterator()) {
            ref = key;
          } else {
            ref = cursor.getPrimaryKey();
          }
        } else {
          if (iterator.isKeyIterator()) {
            ref = key;
          } else {
            ref = cursor.getValue();
          }
        }
        callback(ref);
        //console.log(['onNext', key, primaryKey, value, ref, adv]);
        cursor.advance(1);

      } else {
        cb(undefined);
        callback = null;
      }
    };

  }, stores, ydn.db.base.TransactionMode.READ_ONLY);

  return df;
};


/**
 * @inheritDoc
 */
ydn.db.core.DbOperator.prototype.reduce = function(iterator, callback,
                                                   opt_initial) {

  var me = this;
  var stores = iterator.stores();
  for (var store, i = 0; store = stores[i]; i++) {
    if (!store) {
      throw new ydn.debug.error.ArgumentException('Store "' + store +
          '" not found.');
    }
  }
  var df = this.tx_thread.request(ydn.db.Request.Method.REDUCE, stores);

  var previous = goog.isObject(opt_initial) ?
      ydn.object.clone(opt_initial) : opt_initial;
  this.logger.finer('reduce:' + iterator);
  this.tx_thread.exec(df, function(tx, tx_no, cb) {


    var names = iterator.stores();
    var crs = [];
    for (var ni = 0; ni < names.length; ni++) {
      crs[ni] = me.getIndexExecutor().getCursor(tx, tx_no, names[ni]);
    }
    var cursor = iterator.load(crs);

    /**
     *
     * @param {!Error} e error.
     */
    cursor.onFail = function(e) {
      cb(e, true);
    };
    var index = 0;
    /**
     *
     * @param {IDBKey=} opt_key effective key.
     */
    cursor.onNext = function(opt_key) {
      if (goog.isDefAndNotNull(opt_key)) {
        var current_value;
        if (iterator.isIndexIterator()) {
          if (iterator.isKeyIterator()) {
            current_value = opt_key;
          } else {
            current_value = cursor.getPrimaryKey();
          }
        } else {
          if (iterator.isKeyIterator()) {
            current_value = opt_key;
          } else {
            current_value = cursor.getValue();
          }
        }

        //console.log([previous, current_value, index]);
        previous = callback(previous, current_value, index++);
        cursor.advance(1);
      } else {
        cb(previous);
      }
    };

  }, stores, ydn.db.base.TransactionMode.READ_ONLY);

  return df;
};


/**
 * List record in a store.
 * @param {ydn.db.base.QueryMethod} mth keys method.
 * @param {!ydn.db.Iterator} iter iterator.
 * @param {number=} opt_limit limit.
 * @param {number=} opt_offset limit.
 * @return {!ydn.db.Request} request.
 */
ydn.db.core.DbOperator.prototype.listIter = function(mth, iter,
                                                     opt_limit, opt_offset) {
  var offset = opt_offset || 0;
  var store_name = iter.getStoreName();
  var index_name = iter.getIndexName() || null;
  var limit = opt_limit || ydn.db.base.DEFAULT_RESULT_LIMIT;
  this.logger.finer('listIter:' + mth + ' ' + iter +
      (opt_limit ? ' limit=' + limit : '') +
      (opt_offset ? ' offset=' + offset : ''));
  var method = ydn.db.Request.Method.VALUES_INDEX;
  var req = this.tx_thread.request(method, [store_name]);
  // store.hook(req, arguments);
  var cursor_position =
      (iter.getState() == ydn.db.Iterator.State.COMPLETED ||
          iter.getState() == ydn.db.Iterator.State.INITIAL) ?
      [] : [iter.getKey(), iter.getPrimaryKey()];
  req.addTxback(function() {
    var e_key = iter.getKey();
    this.getCrudExecutor().list(req, mth, store_name,
        index_name, iter.getKeyRange(), limit,
        offset, iter.isReversed(), iter.isUnique(), cursor_position);
  }, this);
  req.addCallback(function() {
    if (goog.isDefAndNotNull(cursor_position[0])) {
      iter.reset(ydn.db.Iterator.State.RESTING,
          cursor_position[0], cursor_position[1]);
    } else {
      iter.reset();
    }
  });
  return req;
};


/**
 * List record in a store.
 * @param {ydn.db.base.QueryMethod} mth keys method.
 * @param {ydn.db.Request} rq request.
 * @param {!ydn.db.core.AbstractIterator} iter iterator.
 * @param {number=} opt_limit limit.
 * @param {number=} opt_offset limit.
 * @protected
 */
ydn.db.core.DbOperator.prototype.iterate = function(mth, rq, iter,
                                                    opt_limit, opt_offset) {
  var arr = [];

  var tx = rq.getTx();
  var tx_no = rq.getLabel();
  var msg = tx_no + ' ' + mth + 'ByIterator ' + iter;
  if (opt_limit > 0) {
    msg += ' limit ' + opt_limit;
  }
  var me = this;
  this.logger.finer(msg);
  var executor = this.getIndexExecutor();
  var cursors = [];
  var store_names = iter.stores();
  for (var i = 0; i < store_names.length; i++) {
    cursors[i] = executor.getCursor(tx, tx_no, store_names[i]);
  }
  var cursor = iter.load(cursors);
  cursor.onFail = function(e) {
    cursor.exit();
    rq.setDbValue(e, true);
  };
  var count = 0;
  var cued = false;
  var displayed = false;
  /**
   * @param {IDBKey=} opt_key
   */
  cursor.onNext = function(opt_key) {
    if (!displayed) {
      me.logger.finest(msg + ' starting');
      displayed = true;
    }
    if (goog.isDefAndNotNull(opt_key)) {
      var primary_key = cursor.getPrimaryKey();
      if (!cued && opt_offset > 0) {
        cursor.advance(opt_offset);
        cued = true;
        return;
      }
      count++;
      if (mth == ydn.db.base.QueryMethod.LIST_KEY) {
        arr.push(opt_key);
      } else if (mth == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
        arr.push(cursor.getPrimaryKey());
      } else if (mth == ydn.db.base.QueryMethod.LIST_KEYS) {
        arr.push([opt_key, cursor.getPrimaryKey()]);
      } else if (mth == ydn.db.base.QueryMethod.COUNT) {
        // no result needed.
      } else {
        // LIST_VALUE
        arr.push(cursor.getValue());
      }
      // console.log(count, cursor);
      if (mth == ydn.db.base.QueryMethod.GET) {
        cursor.exit();
        rq.setDbValue(arr[0]);
      } else if (mth == ydn.db.base.QueryMethod.COUNT ||
          !goog.isDef(opt_limit) || count < opt_limit) {
        cursor.continueEffectiveKey();
      } else {
        me.logger.finer('success:' + msg + ' yields ' + arr.length +
            ' records');
        cursor.exit();
        rq.setDbValue(arr);
      }
    } else {
      me.logger.finer('success:' + msg + ' yields ' + arr.length + ' records');
      cursor.exit();
      var result =
          mth == ydn.db.base.QueryMethod.GET ? arr[0] :
              mth == ydn.db.base.QueryMethod.COUNT ? count : arr;
      rq.setDbValue(result);
    }
  };
};




// Input 53
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A base class for event objects.
 *
 */


goog.provide('goog.events.Event');
goog.provide('goog.events.EventLike');

// goog.events.Event no longer depends on goog.Disposable. Keep requiring
// goog.Disposable here to not break projects which assume this dependency.
goog.require('goog.Disposable');


/**
 * A typedef for event like objects that are dispatchable via the
 * goog.events.dispatchEvent function. strings are treated as the type for a
 * goog.events.Event. Objects are treated as an extension of a new
 * goog.events.Event with the type property of the object being used as the type
 * of the Event.
 * @typedef {string|Object|goog.events.Event}
 */
goog.events.EventLike;



/**
 * A base class for event objects, so that they can support preventDefault and
 * stopPropagation.
 *
 * @param {string} type Event Type.
 * @param {Object=} opt_target Reference to the object that is the target of
 *     this event. It has to implement the {@code EventTarget} interface
 *     declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
 * @constructor
 */
goog.events.Event = function(type, opt_target) {
  /**
   * Event type.
   * @type {string}
   */
  this.type = type;

  /**
   * Target of the event.
   * @type {Object|undefined}
   */
  this.target = opt_target;

  /**
   * Object that had the listener attached.
   * @type {Object|undefined}
   */
  this.currentTarget = this.target;
};


/**
 * For backwards compatibility (goog.events.Event used to inherit
 * goog.Disposable).
 * @deprecated Events don't need to be disposed.
 */
goog.events.Event.prototype.disposeInternal = function() {
};


/**
 * For backwards compatibility (goog.events.Event used to inherit
 * goog.Disposable).
 * @deprecated Events don't need to be disposed.
 */
goog.events.Event.prototype.dispose = function() {
};


/**
 * Whether to cancel the event in internal capture/bubble processing for IE.
 * @type {boolean}
 * @suppress {underscore} Technically public, but referencing this outside
 *     this package is strongly discouraged.
 */
goog.events.Event.prototype.propagationStopped_ = false;


/**
 * Whether the default action has been prevented.
 * This is a property to match the W3C specification at {@link
 * http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-defaultPrevented}.
 * Must be treated as read-only outside the class.
 * @type {boolean}
 */
goog.events.Event.prototype.defaultPrevented = false;


/**
 * Return value for in internal capture/bubble processing for IE.
 * @type {boolean}
 * @suppress {underscore} Technically public, but referencing this outside
 *     this package is strongly discouraged.
 */
goog.events.Event.prototype.returnValue_ = true;


/**
 * Stops event propagation.
 */
goog.events.Event.prototype.stopPropagation = function() {
  this.propagationStopped_ = true;
};


/**
 * Prevents the default action, for example a link redirecting to a url.
 */
goog.events.Event.prototype.preventDefault = function() {
  this.defaultPrevented = true;
  this.returnValue_ = false;
};


/**
 * Stops the propagation of the event. It is equivalent to
 * {@code e.stopPropagation()}, but can be used as the callback argument of
 * {@link goog.events.listen} without declaring another function.
 * @param {!goog.events.Event} e An event.
 */
goog.events.Event.stopPropagation = function(e) {
  e.stopPropagation();
};


/**
 * Prevents the default action. It is equivalent to
 * {@code e.preventDefault()}, but can be used as the callback argument of
 * {@link goog.events.listen} without declaring another function.
 * @param {!goog.events.Event} e An event.
 */
goog.events.Event.preventDefault = function(e) {
  e.preventDefault();
};

// Input 54
/**
 * @fileoverview Event dispatch from Storage.
 *
 * User: kyawtun
 * Date: 20/10/12
 */


goog.provide('ydn.db.events.RecordEvent');
goog.provide('ydn.db.events.StorageEvent');
goog.provide('ydn.db.events.StoreEvent');
goog.provide('ydn.db.events.Types');
goog.require('goog.events.Event');


/**
 * Event types.
 *
 * Note: these event type string are exported.
 * @enum {string}
 */
ydn.db.events.Types = {
  READY: 'ready',
  ERROR: 'error',
  FAIL: 'fail',
  CREATED: 'created',
  DELETED: 'deleted',
  UPDATED: 'updated'
};



/**
 *
 * @param {ydn.db.events.Types} event_type event type.
 * @param {Object=} event_target target.
 * @extends {goog.events.Event}
 * @constructor
 * @struct
 * @suppress {checkStructDictInheritance} suppress closure-library code.
 */
ydn.db.events.Event = function(event_type, event_target) {
  goog.base(this, event_type, event_target);

};
goog.inherits(ydn.db.events.Event, goog.events.Event);


/**
 * @type {string}
 */
ydn.db.events.Event.prototype.store_name;


/**
 *
 * @return {string} effected store name.
 */
ydn.db.events.Event.prototype.getStoreName = function() {
  return this.store_name;
};



/**
 *
 * @param {ydn.db.events.Types} event_type type.
 * @param {Object} event_target event target.
 * @param {number} version source.
 * @param {number} old_version old version.
 * @param {Object} old_schema old schema read from the database.
 * @extends {ydn.db.events.Event}
 * @constructor
 * @struct
 * @suppress {checkStructDictInheritance} suppress closure-library code.
 */
ydn.db.events.StorageEvent = function(event_type, event_target, version,
                                      old_version, old_schema) {
  goog.base(this, event_type, event_target);
  this.version = version;
  this.oldVersion = old_version;
  this.old_schema_ = old_schema;
};
goog.inherits(ydn.db.events.StorageEvent, ydn.db.events.Event);


/**
 * @final
 * @type {string}
 */
ydn.db.events.StorageEvent.prototype.name = 'ReadyEvent';


/**
 *
 * @type {number}
 */
ydn.db.events.StorageEvent.prototype.version = NaN;


/**
 *
 * @type {number}
 */
ydn.db.events.StorageEvent.prototype.oldVersion = NaN;


/**
 *
 * @type {Object}
 */
ydn.db.events.StorageEvent.prototype.old_schema_ = null;


/**
 *
 * @return {number} return current version.
 */
ydn.db.events.StorageEvent.prototype.getVersion = function() {
  return this.version;
};


/**
 *
 * @return {number} return previous version.
 */
ydn.db.events.StorageEvent.prototype.getOldVersion = function() {
  return this.oldVersion;
};


/**
 * @return {Object} return schema read from the database.
 */
ydn.db.events.StorageEvent.prototype.getOldSchema = function() {
  return this.old_schema_;
};



/**
 * Storage error event.
 * @param {Object} event_target event target.
 * @param {Error} error error object in case of error.
 * @param {ydn.db.events.Types=} opt_type default to error.
 * @extends {ydn.db.events.Event}
 * @constructor
 * @struct
 */
ydn.db.events.StorageErrorEvent = function(event_target, error, opt_type) {
  var type = opt_type || ydn.db.events.Types.ERROR;
  goog.base(this, type, event_target);
  this.error = error;
};
goog.inherits(ydn.db.events.StorageErrorEvent, ydn.db.events.Event);


/**
 * @return {string}
 */
ydn.db.events.StorageErrorEvent.prototype.toString = function() {
  return this.name + ':' + (this.error ? this.error : '');
};


/**
 * @type {string}
 */
ydn.db.events.StorageErrorEvent.prototype.name = 'ErrorEvent';


/**
 *
 * @type {Error}
 */
ydn.db.events.StorageErrorEvent.prototype.error = null;


/**
 * @return {Error} return error if connection was an error.
 */
ydn.db.events.StorageErrorEvent.prototype.getError = function() {
  return this.error;
};



/**
 * Storage terminal fail event.
 * @param {Object} event_target event target.
 * @param {Error} error error object in case of error.
 * @extends {ydn.db.events.StorageErrorEvent}
 * @constructor
 * @struct
 */
ydn.db.events.StorageFailEvent = function(event_target, error) {
  goog.base(this, event_target, error, ydn.db.events.Types.FAIL);
};
goog.inherits(ydn.db.events.StorageFailEvent, ydn.db.events.StorageErrorEvent);


/**
 * @final
 * @type {string}
 */
ydn.db.events.StorageFailEvent.prototype.name = 'FailEvent';



/**
 *
 * @param {ydn.db.events.Types} event_type  type.
 * @param {Object} event_target target.
 * @param {string} store_name source.
 * @param {*} key source.
 * @param {*} value source.
 * @extends {ydn.db.events.Event}
 * @constructor
 * @struct
 */
ydn.db.events.RecordEvent = function(event_type, event_target, store_name, key,
                                     value) {
  goog.base(this, event_type, event_target);
  this.store_name = store_name;
  this.key = key;
  this.value = value;
};
goog.inherits(ydn.db.events.RecordEvent, ydn.db.events.Event);


/**
 * @final
 * @type {string}
 */
ydn.db.events.RecordEvent.prototype.name = 'RecordEvent';


/**
 *
 * @type {*}
 */
ydn.db.events.RecordEvent.prototype.key;


/**
 *
 * @type {*}
 */
ydn.db.events.RecordEvent.prototype.value;


/**
 *
 * @return {*} key.
 */
ydn.db.events.RecordEvent.prototype.getKey = function() {
  return this.key;
};


/**
 *
 * @return {*} value.
 */
ydn.db.events.RecordEvent.prototype.getValue = function() {
  return this.value;
};



/**
 *
 * @param {ydn.db.events.Types} event_type  type.
 * @param {Object} event_target target.
 * @param {string} store_name source.
 * @param {Array} keys source.
 * @param {Array=} opt_values source.
 * @extends {ydn.db.events.Event}
 * @constructor
 * @struct
 */
ydn.db.events.StoreEvent = function(event_type, event_target, store_name, keys,
                                    opt_values) {
  goog.base(this, event_type, event_target);
  this.store_name = store_name;
  this.keys = keys;
  this.values = opt_values;
};
goog.inherits(ydn.db.events.StoreEvent, ydn.db.events.Event);


/**
 * @final
 * @type {string}
 */
ydn.db.events.StoreEvent.prototype.name = 'StoreEvent';


/**
 *
 * @type {Array}
 */
ydn.db.events.StoreEvent.prototype.keys;


/**
 *
 * @type {Array|undefined}
 */
ydn.db.events.StoreEvent.prototype.values;


/**
 *
 * @return {*} get list of keys.
 */
ydn.db.events.StoreEvent.prototype.getKeys = function() {
  return this.keys;
};


/**
 *
 * @return {*} get list of values.
 */
ydn.db.events.StoreEvent.prototype.getValues = function() {
  return this.values;
};



// Input 55
/**
 * @fileoverview Interface for database connector.
 */


goog.provide('ydn.db.con.IDatabase');
goog.require('goog.async.Deferred');



/**
 * @interface
 */
ydn.db.con.IDatabase = function() {};


/**
 * Close the connection.
 */
ydn.db.con.IDatabase.prototype.close = function() {};


/**
 * Return readable representation of storage mechanism. It should be all lower
 * case and use in type checking.
 * @return {string} connected database type.
 */
ydn.db.con.IDatabase.prototype.getType = function() {};


/**
 * @return {boolean} ready status.
 */
ydn.db.con.IDatabase.prototype.isReady = function() {};


/**
 * @param {string} name database name.
 * @param {!ydn.db.schema.Database} schema database schema.
 * @return {!goog.async.Deferred} promise on connected.
 */
ydn.db.con.IDatabase.prototype.connect = function(name, schema) {};


/**
 * @return {number|undefined} return current version;
 */
ydn.db.con.IDatabase.prototype.getVersion = goog.abstractMethod;


/**
 * @return {*} underlying database.
 */
ydn.db.con.IDatabase.prototype.getDbInstance = function() {};


/**
 * Perform transaction immediately and invoke transaction_callback with
 * the transaction object.
 * Database adaptor must invoke completed_event_handler
 * when the data is transaction completed.
 * Caller must not invoke this method until transaction completed event is
 * fired.
 * @param {function(ydn.db.base.Transaction)}
  * transaction_callback callback function that invoke in the transaction with
 * transaction instance.
 * @param {Array.<string>} store_names list of store names involved in the
 * transaction.
 * @param {ydn.db.base.TransactionMode} mode mode, default to 'read_write'.
 * @param {function(ydn.db.base.TxEventTypes, *)}
  * completed_event_handler handler for on completed event.
 */
ydn.db.con.IDatabase.prototype.doTransaction = goog.abstractMethod;


/**
 *
 * @param {function(ydn.db.schema.Database)} callback database schema obtained
 * by reflecting connected database.
 * @param {(SQLTransaction|IDBTransaction|Object)=} trans transaction to reuse.
 * @param {(IDBDatabase|Database)=} db database to reuse.
 */
ydn.db.con.IDatabase.prototype.getSchema = goog.abstractMethod;


/**
 * On terminal failure.
 * @type {Function}
 */
ydn.db.con.IDatabase.prototype.onFail = goog.abstractMethod;


/**
 * On terminal failure.
 * @type {Function}
 */
ydn.db.con.IDatabase.prototype.onError = goog.abstractMethod;


/**
 * On terminal failure.
 * @type {Function}
 */
ydn.db.con.IDatabase.prototype.onVersionChange = goog.abstractMethod;


// Input 56
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Database index schema.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.schema.DataType');
goog.provide('ydn.db.schema.Index');
goog.require('ydn.db.base');
goog.require('ydn.db.utils');
goog.require('ydn.debug.error.ArgumentException');



/**
 * Schema for index.
 *
 * @param {string|!Array.<string>} keyPath the key path.
 * @param {string|ydn.db.schema.DataType=} opt_type to be determined.
 * @param {boolean=} opt_unique True if the index enforces that there is only
 * one objectfor each unique value it indexes on.
 * @param {boolean=} opt_multi_entry specifies whether the index's multiEntry
 * flag is set.
 * @param {string=} opt_index_name index name.
 * @param {Function=} opt_generator index key generator.
 * @constructor
 * @struct
 */
ydn.db.schema.Index = function(
    keyPath, opt_type, opt_unique, opt_multi_entry, opt_index_name,
    opt_generator) {

  if (!goog.isDef(opt_index_name)) {
    if (goog.isArray(keyPath)) {
      opt_index_name = keyPath.join(', ');
    } else {
      opt_index_name = keyPath;
    }
  }

  if (goog.isDefAndNotNull(keyPath) && !goog.isString(keyPath) &&
      !goog.isArrayLike(keyPath)) {
    throw new ydn.debug.error.ArgumentException('index keyPath for ' +
        opt_index_name +
        ' must be a string or array, but ' + keyPath + ' is ' + typeof keyPath);
  }

  if (goog.DEBUG && goog.isArray(keyPath) && Object.freeze) {
    // NOTE: due to performance penalty (in Chrome) of using freeze and
    // hard to debug on different browser we don't want to use freeze
    // this is experimental.
    // http://news.ycombinator.com/item?id=4415981
    Object.freeze(/** @type {!Object} */ (keyPath));
  }

  if (!goog.isDef(keyPath) && goog.isDef(opt_index_name)) {
    keyPath = opt_index_name;
  }

  /**
   * @final
   */
  this.keyPath = keyPath;
  /**
   * @final
   * @private
   */
  this.is_composite_ = goog.isArrayLike(this.keyPath);

  /**
   * @private
   * @final
   * @type {string}
   */
  this.index_name_ = opt_index_name;
  /**
   * @final
   * @type {ydn.db.schema.DataType|undefined}
   */
  this.type = ydn.db.schema.Index.toType(opt_type);
  if (goog.isDef(opt_type)) {
    if (!goog.isDef(this.type)) {
      throw new ydn.debug.error.ArgumentException('type invalid in index: ' +
          this.index_name_);
    }
    if (goog.isArray(this.keyPath)) {
      throw new ydn.debug.error.ArgumentException(
          'composite key for store "' + this.index_name_ +
          '" must not specified type');
    }
  }
  /**
   * @final
   */
  this.unique = !!opt_unique;

  /**
   * @final
   */
  this.multiEntry = !!opt_multi_entry;
  /**
   * @final
   * @private
   */
  this.keyColumnType_ = goog.isString(this.type) ? this.type :
      ydn.db.schema.DataType.TEXT;
  /**
   * @final
   * @private
   */
  this.index_column_name_ = goog.isString(opt_index_name) ?
      opt_index_name : goog.isArray(keyPath) ?
          this.keyPath.join(',') : keyPath;

  this.index_column_name_quoted_ = goog.string.quote(this.index_column_name_);

  this.key_paths_ = !this.is_composite_ && !this.multiEntry ?
      this.keyPath.split('.') : null;

  goog.asserts.assert(!goog.isDefAndNotNull(opt_generator) ||
      goog.isFunction(opt_generator), 'index generator must be a function, ' +
      ', but ' + (typeof opt_generator) + ' found.');
  /**
   * @private
   */
  this.index_generator_ = opt_generator || null;
};


/**
 * Extract value of keyPath from a given object.
 * @param {!Object} obj object to extract from.
 * @return {IDBKey|undefined} return key value.
 */
ydn.db.schema.Index.prototype.extractKey = function(obj) {
  if (goog.isDefAndNotNull(obj)) {
    if (goog.isArrayLike(this.keyPath)) {
      var key = [];
      for (var i = 0, n = this.keyPath.length; i < n; i++) {
        var i_key = ydn.db.utils.getValueByKeys(obj, this.keyPath[i]);
        goog.asserts.assert(goog.isDefAndNotNull(i_key),
            ydn.json.toShortString(obj) +
            ' does not issue require composite key value ' + i + ' of ' +
            n + ' on index "' + this.index_name_ + '"');
        key[i] = i_key;
      }
      return key;
    } else {
      return /** @type {IDBKey} */ (ydn.db.utils.getValueByKeys(
          obj, this.keyPath));
    }
  }
};


/**
 * Apply index value to given object according to key path.
 * Index must not be composite nor multiEntry.
 * @param {!Object} obj
 * @param {*} value
 */
ydn.db.schema.Index.prototype.applyValue = function(obj, value) {
  for (var i = 0; i < this.key_paths_.length; i++) {
    if (i == this.key_paths_.length - 1) {
      obj[this.key_paths_[i]] = value;
    } else {

      if (!goog.isObject(obj[this.key_paths_[i]])) {
        obj[this.key_paths_[i]] = {};
      }
    }
  }
};


/**
 * @private
 * @type {ydn.db.schema.DataType}
 */
ydn.db.schema.Index.prototype.keyColumnType_;


/**
 * @type {(string|!Array.<string>)}
 */
ydn.db.schema.Index.prototype.keyPath;


/**
 * Cache result of spliting key path by '.'.
 * @type {Array.<string>}
 * @private
 */
ydn.db.schema.Index.prototype.key_paths_;


/**
 * @type {boolean}
 */
ydn.db.schema.Index.prototype.multiEntry;


/**
 * @type {boolean}
 * @private
 */
ydn.db.schema.Index.prototype.is_composite_;


/**
 * @type {boolean}
 */
ydn.db.schema.Index.prototype.unique;


/**
 * Data type for field in object store. This is required to compatible between
 * IndexedDB and SQLite.
 * SQLite mandate COLUMN field specified data type.
 * IndexedDB allow Array as data type in key, while SQLite is not to use.
 * @see http://www.w3.org/TR/IndexedDB/#key-construct
 * @see http://www.sqlite.org/datatype3.html
 * @see http://www.sqlite.org/lang_expr.html
 * @enum {string}
 */
ydn.db.schema.DataType = {
  BLOB: 'BLOB',
  DATE: 'DATE',
  INTEGER: 'INTEGER', // AUTOINCREMENT is only allowed on an INTEGER
  NUMERIC: 'NUMERIC',
  TEXT: 'TEXT'
};


/**
 * This data type abbreviation is used to prefix value of
 * ydn.db.schema.DataType.ARRAY
 * on storage.
 * @see http://www.sqlite.org/datatype3.html
 * @enum {string}
 */
ydn.db.DataTypeAbbr = {
  DATE: 'd',
  NUMERIC: 'n',
  TEXT: 't',
  BLOB: 'b'
};


/**
 * Seperator char for array
 * @const
 * @type {string}
 */
ydn.db.schema.Index.ARRAY_SEP = String.fromCharCode(0x001F);


/**
 * Convert key value from IndexedDB value to Sqlite for storage.
 * @see #sql2js
 * @param {Array|Date|*} key key.
 * @param {ydn.db.schema.DataType|undefined} type data type.
 * @return {*} string.
 */
ydn.db.schema.Index.js2sql = function(key, type) {
  if (type == ydn.db.schema.DataType.DATE) {
    if (key instanceof Date) {
      return +key;  // date is store as NUMERIC
    } // else ?
  } else if (goog.isDefAndNotNull(type)) {
    return key; // NUMERIC, INTEGER, and BLOB
  } else {
    return ydn.db.utils.encodeKey(key);
  }
};


/**
 * Convert key value from Sqlite value to IndexedDB for storage.
 * @see #js2sql
 * @param {string|number|*} key key.
 * @param {ydn.db.schema.DataType|undefined} type type.
 * @return {IDBKey|undefined} decoded key.
 */
ydn.db.schema.Index.sql2js = function(key, type) {
  if (type == ydn.db.schema.DataType.DATE) {
    return new Date(key); // key is number
  } else if (goog.isDef(type)) {
    return /** @type {number} */ (key);   // NUMERIC, INTEGER, BLOB
  } else {
    return ydn.db.utils.decodeKey(/** @type {string} */ (key));
  }
};


/**
 * @const
 * @type {!Array.<ydn.db.schema.DataType>} column data type.
 */
ydn.db.schema.Index.TYPES = [
  ydn.db.schema.DataType.BLOB,
  ydn.db.schema.DataType.DATE,
  ydn.db.schema.DataType.INTEGER,
  ydn.db.schema.DataType.NUMERIC,
  ydn.db.schema.DataType.TEXT];


/**
 * Return an immutable type.
 * @param {ydn.db.schema.DataType|string=} opt_type data type in string.
 * @return {ydn.db.schema.DataType|undefined}
 * data type.
 */
ydn.db.schema.Index.toType = function(opt_type) {
  if (goog.isString(opt_type)) {
    var idx = goog.array.indexOf(ydn.db.schema.Index.TYPES, opt_type);
    return ydn.db.schema.Index.TYPES[idx]; // undefined OK.
  } else {
    return undefined;
  }

};


/**
 *
 * @param {*} x object to test.
 * @return {ydn.db.DataTypeAbbr} type of object type.
 */
ydn.db.schema.Index.toAbbrType = function(x) {
  if (x instanceof Date) {
    return ydn.db.DataTypeAbbr.DATE;
  } else if (goog.isNumber(x)) {
    return ydn.db.DataTypeAbbr.NUMERIC;
  } else if (goog.isString(x)) {
    return ydn.db.DataTypeAbbr.TEXT;
  } else {
    return ydn.db.DataTypeAbbr.BLOB;
  }
};


/**
 *
 * @param {*} x object to test.
 * @return {ydn.db.DataTypeAbbr} type of object type.
 */
ydn.db.schema.Index.type2AbbrType = function(x) {
  if (x === ydn.db.schema.DataType.DATE) {
    return ydn.db.DataTypeAbbr.DATE;
  } else if (x === ydn.db.schema.DataType.NUMERIC) {
    return ydn.db.DataTypeAbbr.NUMERIC;
  } else if (x === ydn.db.schema.DataType.TEXT) {
    return ydn.db.DataTypeAbbr.TEXT;
  } else {
    return ydn.db.DataTypeAbbr.BLOB;
  }
};


/**
 * Return type.
 * @return {ydn.db.schema.DataType|undefined} data type.
 */
ydn.db.schema.Index.prototype.getType = function() {
  return this.type;
};


/**
 *
 * @return {ydn.db.schema.DataType} get type suitable to use in SQL query
 * construction.
 */
ydn.db.schema.Index.prototype.getSqlType = function() {
  return this.keyColumnType_;
};


/**
 *
 * @return {string} index name.
 */
ydn.db.schema.Index.prototype.getName = function() {
  return this.index_name_;
};


/**
 *
 * @return {boolean} multiEntry or not.
 */
ydn.db.schema.Index.prototype.isMultiEntry = function() {
  return this.multiEntry;
};


/**
 *
 * @return {boolean} composite index or not.
 */
ydn.db.schema.Index.prototype.isComposite = function() {
  return this.is_composite_;
};


/**
 *
 * @return {boolean} unique or not.
 */
ydn.db.schema.Index.prototype.isUnique = function() {
  return this.unique;
};


/**
 * @inheritDoc
 */
ydn.db.schema.Index.prototype.toJSON = function() {
  return {
    'name': this.index_name_,
    'keyPath': this.keyPath,
    'type': this.type,
    'unique': this.unique,
    'multiEntry': this.multiEntry
  };
};


/**
 *
 * @return {!ydn.db.schema.Index} a clone.
 */
ydn.db.schema.Index.prototype.clone = function() {
  var keyPath = goog.isArray(this.keyPath) ?
      goog.array.clone(/** @type {goog.array.ArrayLike} */ (this.keyPath)) :
      this.keyPath;

  return new ydn.db.schema.Index(
      keyPath,
      this.type,
      this.unique,
      this.multiEntry,
      this.index_name_,
      this.index_generator_);
};


/**
 * Compare two keyPath.
 * @see #equals
 * @param {*} keyPath1 key path 1.
 * @param {*} keyPath2 key path 1.
 * @return {string?} description where is different between the two. null
 * indicate similar schema.
 */
ydn.db.schema.Index.compareKeyPath = function(keyPath1, keyPath2) {
  if (!goog.isDefAndNotNull(keyPath1) && !goog.isDefAndNotNull(keyPath2)) {
    return null;
  } else if (!goog.isDefAndNotNull(keyPath1)) {
    return 'newly define ' + keyPath2;
  } else if (!goog.isDefAndNotNull(keyPath2)) {
    return 'keyPath: ' + keyPath1 + ' no longer defined';
  } else if (goog.isArrayLike(keyPath1) && goog.isArrayLike(keyPath2)) {
    return goog.array.equals(/** @type {goog.array.ArrayLike} */ (keyPath1),
        /** @type {goog.array.ArrayLike} */ (keyPath2)) ?
        null : 'expect: ' + keyPath1 + ', but: ' + keyPath2;
  } else if (!ydn.object.equals(keyPath1, keyPath2)) {
    return 'expect: ' + keyPath1 + ', but: ' + keyPath2;
  } else {
    return null;
  }
};


/**
 * Test key path.
 * @param {string|!Array.<string>} key_path key path to be tested.
 * @return {boolean} true if given key path is equal to this key path.
 */
ydn.db.schema.Index.prototype.equalsKeyPath = function(key_path) {
  return !ydn.db.schema.Index.compareKeyPath(this.keyPath, key_path);
};


/**
 * Compare two stores.
 * @see #equals
 * @param {ydn.db.schema.Index} index index schema to test.
 * @return {string} description where is different between the two. Empty string
 * indicate similar schema.
 */
ydn.db.schema.Index.prototype.difference = function(index) {
  if (!index) {
    return 'no index for ' + this.index_name_;
  }
  if (this.index_name_ != index.index_name_) {
    return 'name, expect: ' + this.index_name_ + ', but: ' + index.index_name_;
  }
  var msg = ydn.db.schema.Index.compareKeyPath(this.keyPath, index.keyPath);
  if (msg) {
    return 'keyPath, ' + msg;
  }
  if (goog.isDefAndNotNull(this.unique) &&
      goog.isDefAndNotNull(index.unique) &&
      this.unique != index.unique) {
    return 'unique, expect: ' + this.unique + ', but: ' + index.unique;
  }
  if (goog.isDefAndNotNull(this.multiEntry) &&
      goog.isDefAndNotNull(index.multiEntry) &&
      this.multiEntry != index.multiEntry) {
    return 'multiEntry, expect: ' + this.multiEntry +
        ', but: ' + index.multiEntry;
  }
  if (goog.isDef(this.type) && goog.isDef(index.type) &&
      (goog.isArrayLike(this.type) ? !goog.array.equals(
      /** @type {goog.array.ArrayLike} */ (this.type),
      /** @type {goog.array.ArrayLike} */ (index.type)) :
      this.type != index.type)) {
    return 'data type, expect: ' + this.type + ', but: ' + index.type;
  }
  return '';
};


/**
 * Create a new update index schema with given guided index schema.
 * NOTE: This is used in websql for checking table schema sniffed from the
 * connection is similar to requested table schema. The fact is that
 * some schema information are not able to reconstruct from the connection,
 * these include:
 *   1. composite index: in which a composite index is blown up to multiple
 *     columns. @see ydn.db.con.WebSql.prototype.prepareTableSchema_.
 * @param {ydn.db.schema.Index} that guided index schema.
 * @return {!ydn.db.schema.Index} updated index schema.
 */
ydn.db.schema.Index.prototype.hint = function(that) {
  if (!that) {
    return this;
  }
  goog.asserts.assert(this.index_name_ == that.index_name_, 'index name: ' +
      this.index_name_ + ' != ' + that.index_name_);
  var keyPath = goog.isArray(this.keyPath) ?
      goog.array.clone(/** @type {goog.array.ArrayLike} */ (this.keyPath)) :
      this.keyPath;
  var type = this.type;
  if (!goog.isDef(that.type) && type == 'TEXT') {
    // composite are converted into TEXT
    type = undefined;
  }
  return new ydn.db.schema.Index(keyPath, type, this.unique, this.multiEntry,
      that.index_name_);
};


/**
 *
 * @param {ydn.db.base.Direction|string=} opt_str direction in string format.
 * @return {ydn.db.base.Direction|undefined} equivalent typed direction.
 */
ydn.db.schema.Index.toDir = function(opt_str) {
  var idx = goog.array.indexOf(ydn.db.base.DIRECTIONS, opt_str);
  return ydn.db.base.DIRECTIONS[idx]; // undefined OK.
};


/**
 *
 * @return {(string|!Array.<string>)} keyPath.
 */
ydn.db.schema.Index.prototype.getKeyPath = function() {
  return this.keyPath;
};


/**
 * Return quoted keyPath. In case undefined return default key column.
 * @return {string} return quoted keyPath. If keyPath is array, they are
 * join by ',' and quoted. If keyPath is not define, default sqlite column
 * name is used.
 */
ydn.db.schema.Index.prototype.getSQLIndexColumnName = function() {
  return this.index_column_name_;
};


/**
 * Return quoted keyPath. In case undefined return default key column.
 * @return {string} return quoted keyPath. If keyPath is array, they are
 * join by ',' and quoted. If keyPath is not define, default sqlite column
 * name is used.
 */
ydn.db.schema.Index.prototype.getSQLIndexColumnNameQuoted = function() {
  return this.index_column_name_quoted_;
};


/**
 * @type {string}
 * @private
 */
ydn.db.schema.Index.prototype.index_column_name_;


/**
 * @type {string}
 * @private
 */
ydn.db.schema.Index.prototype.index_column_name_quoted_;


/**
 * @type {Function}
 * @private
 */
ydn.db.schema.Index.prototype.index_generator_;


/**
 * Set a generator function.
 * @param {Function} gen generator function.
 */
ydn.db.schema.Index.prototype.setGenerator = function(gen) {
  goog.asserts.assert(!this.index_generator_, 'index ' + this.index_name_ +
      ' already has a generator');
  this.index_generator_ = gen;
};


/**
 * Add index by generator.
 * @param {Object} obj record value.
 */
ydn.db.schema.Index.prototype.generateIndex = function(obj) {
  if (this.index_generator_) {
    var out = this.index_generator_(obj);
    var type = typeof(out);
    if (type == 'string' || type == 'number' || type == 'array' ||
        type == 'undefined' || out instanceof Date) {
      for (var i = 0; i < this.key_paths_.length - 1; i++) {
        if (!goog.isObject(obj[this.key_paths_[i]])) {
          obj[this.key_paths_[i]] = {};
        }
      }
      obj[this.key_paths_[this.key_paths_.length - 1]] = out;
    }
  }
};


/**
 *
 * @param {!IndexSchema} json object in json format.
 * @return {ydn.db.schema.Index} created from input json string.
 */
ydn.db.schema.Index.fromJSON = function(json) {
  if (goog.DEBUG) {
    var fields = ['name', 'unique', 'type', 'keyPath', 'multiEntry',
      'generator'];
    for (var key in json) {
      if (json.hasOwnProperty(key) && goog.array.indexOf(fields, key) == -1) {
        throw new ydn.debug.error.ArgumentException('Unknown field: ' + key +
            ' in ' + ydn.json.stringify(json));
      }
    }
  }
  return new ydn.db.schema.Index(json.keyPath, json.type, json.unique,
      json.multiEntry, json.name, json.generator);
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.schema.Index.prototype.toString = function() {
    var s = this.multiEntry ? 'MultiEntry' : '';
    if (this.key_paths_ && this.key_paths_.length > 1) {
      s += 'Compound';
    }
    return s + 'Index:' + this.index_name_;
  };
}


// Input 57
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Represent object store.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.schema.Store');
goog.require('goog.array.ArrayLike');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Request.Method');
goog.require('ydn.db.schema.Index');



/**
 * Create a store schema.
 * @param {string} name object store name or TABLE name.
 * @param {(Array.<string>|string)=} opt_key_path indexedDB keyPath, like
 * 'feed.id.$t'. A path to extract primary key from record value.
 * @param {boolean=} opt_autoIncrement If true, the object store has a key
 * generator. Defaults to false.
 * @param {string|ydn.db.schema.DataType=} opt_type data type for keyPath. This
 * value is only used by WebSQL for column data type.
 * <code>ydn.db.schema.DataType.INTEGER</code> if opt_autoIncrement is
 * <code>true.</code>
 * @param {!Array.<!ydn.db.schema.Index>=} opt_indexes list of indexes.
 * @param {boolean=} opt_dispatch_events if true, storage instance should
 * dispatch event on record values changes.
 * @param {boolean=} opt_is_fixed fixed store schema. Websql TABLE, has a
 * default column to store JSON stringify data. A fixed store schema TABLE,
 * do not hae that default column.
 * @param {boolean=} opt_encrypted store is encrypted.
 * @constructor
 * @struct
 */
ydn.db.schema.Store = function(name, opt_key_path, opt_autoIncrement, opt_type,
                               opt_indexes, opt_dispatch_events, opt_is_fixed,
                               opt_encrypted) {

  /**
   * @private
   * @final
   * @type {string}
   */
  this.name_ = name;
  if (!goog.isString(this.name_)) {
    throw new ydn.debug.error.ArgumentException('store name must be a string');
  }
  /**
   * @final
   */
  this.keyPath = goog.isDef(opt_key_path) ? opt_key_path : null;
  /**
   * @final
   */
  this.isComposite = goog.isArrayLike(this.keyPath);

  if (!goog.isNull(this.keyPath) &&
      !goog.isString(this.keyPath) && !this.isComposite) {
    throw new ydn.debug.error.ArgumentException(
        'keyPath must be a string or array');
  }

  /**
   * IE10 do not reflect autoIncrement, so that make undefined as an option.
   * @final
   * @type {boolean|undefined}
   */
  this.autoIncrement = opt_autoIncrement;

  var type;
  if (goog.isDefAndNotNull(opt_type)) {
    type = ydn.db.schema.Index.toType(opt_type);
    if (!goog.isDef(type)) {
      throw new ydn.debug.error.ArgumentException('type "' + opt_type +
          '" for primary key in store "' + this.name_ + '" is invalid.');
    }
    if (this.isComposite) {
      throw new ydn.debug.error.ArgumentException(
          'composite key for store "' + this.name_ +
              '" must not specified type');
    }
  }

  /**
   * @final
   */
  this.type = goog.isDefAndNotNull(type) ? type : this.autoIncrement ?
      ydn.db.schema.DataType.INTEGER : undefined;

  /**
   * @final
   */
  this.keyPaths = goog.isString(this.keyPath) ? this.keyPath.split('.') : [];
  /**
   * @final
   */
  this.indexes = opt_indexes || [];
  /**
   * @final
   */
  this.dispatch_events = !!opt_dispatch_events;
  /**
   * @final
   */
  this.fixed = !!opt_is_fixed;
  /**
   * @final
   * @private
   */
  this.keyColumnType_ = goog.isString(this.type) ?
      this.type : ydn.db.schema.DataType.TEXT;
  /**
   * @final
   * @private
   */
  this.primary_column_name_ = goog.isArray(this.keyPath) ?
      this.keyPath.join(',') :
      goog.isString(this.keyPath) ?
          this.keyPath :
          ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME;

  /**
   * @final
   * @private
   */
  this.primary_column_name_quoted_ =
      goog.string.quote(this.primary_column_name_);

  /**
   * @final
   * @type {boolean}
   * @private
   */
  this.is_encrypted_ = !!opt_encrypted;
  if (goog.DEBUG && this.is_encrypted_) {
    if (this.keyPath) {
      throw new ydn.debug.error.ArgumentException('encrypted store "' +
          this.name_ + '" must not use inline key');
    }
    if (this.isAutoIncrement()) {
      throw new ydn.debug.error.ArgumentException('encrypted store "' +
          this.name_ + '" must not use key generator');
    }
  }

  /**
   * @final
   * @type {Array.<function(!ydn.db.Request, goog.array.ArrayLike)>} hookers.
   * @private
   */
  this.hooks_ = [];

};


/**
 * @enum {string}
 */
ydn.db.schema.Store.FetchStrategy = {
  LAST_UPDATED: 'last-updated',
  ASCENDING_KEY: 'ascending-key',
  DESCENDING_KEY: 'descending-key'
};


/**
 * @const
 * @type {Array.<ydn.db.schema.Store.FetchStrategy>}
 */
ydn.db.schema.Store.FetchStrategies = [
  ydn.db.schema.Store.FetchStrategy.LAST_UPDATED,
  ydn.db.schema.Store.FetchStrategy.ASCENDING_KEY,
  ydn.db.schema.Store.FetchStrategy.DESCENDING_KEY];


/**
 * @type {boolean}
 * @private
 */
ydn.db.schema.Store.prototype.isComposite;


/**
 * @type {(!Array.<string>|string)?}
 */
ydn.db.schema.Store.prototype.keyPath;


/**
 * @type {boolean|undefined}
 */
ydn.db.schema.Store.prototype.autoIncrement;


/**
 * @type {ydn.db.schema.DataType|undefined} //
 */
ydn.db.schema.Store.prototype.type;


/**
 * @private
 * @type {ydn.db.schema.DataType}
 */
ydn.db.schema.Store.prototype.keyColumnType_;


/**
 * @protected
 * @type {!Array.<string>}
 */
ydn.db.schema.Store.prototype.keyPaths;


/**
 * @type {!Array.<!ydn.db.schema.Index>}
 * @protected
 */
ydn.db.schema.Store.prototype.indexes;


/**
 * @type {boolean}
 */
ydn.db.schema.Store.prototype.dispatch_events = false;


/**
 * A fixed schema cannot store arbitrary data structure. This is used only
 * in WebSQL. A arbitrery data structure require default blob column.
 * @type {boolean}
 */
ydn.db.schema.Store.prototype.fixed = false;


/**
 * @inheritDoc
 */
ydn.db.schema.Store.prototype.toJSON = function() {

  var indexes = [];
  for (var i = 0; i < this.indexes.length; i++) {
    indexes.push(this.indexes[i].toJSON());
  }

  return {
    'name': this.name_,
    'keyPath': this.keyPath,
    'autoIncrement': this.autoIncrement,
    'type': this.type,
    'indexes': indexes
  };
};


/**
 *
 * @param {!StoreSchema} json Restore from json stream.
 * @return {!ydn.db.schema.Store} create new store schema from JSON string.
 */
ydn.db.schema.Store.fromJSON = function(json) {
  if (goog.DEBUG) {
    var fields = ['name', 'keyPath', 'autoIncrement', 'type', 'indexes',
      'dispatchEvents', 'fixed', 'Sync', 'encrypted'];
    for (var key in json) {
      if (json.hasOwnProperty(key) && goog.array.indexOf(fields, key) == -1) {
        throw new ydn.debug.error.ArgumentException('Unknown attribute "' +
            key + '"');
      }
    }
  }
  var indexes = [];
  var indexes_json = json.indexes || [];
  if (goog.isArray(indexes_json)) {
    for (var i = 0; i < indexes_json.length; i++) {
      var index = ydn.db.schema.Index.fromJSON(indexes_json[i]);
      if (goog.isDef(index.keyPath) && index.keyPath === json.keyPath) {
        continue; // key do not need indexing.
      }
      indexes.push(index);
    }
  }
  var type = json.type === 'undefined' || json.type === 'null' ?
      undefined : json.type;
  return new ydn.db.schema.Store(json.name, json.keyPath, json.autoIncrement,
      type, indexes, json.dispatchEvents, json.fixed, json.encrypted);
};


/**
 *
 * @param {!Array} params sql parameter list.
 * @param {ydn.db.base.QueryMethod} method query method.
 * @param {string|undefined} index_column name.
 * @param {IDBKeyRange} key_range to retrieve.
 * @param {boolean} reverse ordering.
 * @param {boolean} unique unique column.
 * @return {string} sql statement.
 */
ydn.db.schema.Store.prototype.toSql = function(params, method, index_column,
    key_range, reverse, unique) {
  var out = this.inSql(params, method, index_column,
      key_range, reverse, unique);
  var sql = '';

  if (method != ydn.db.base.QueryMethod.NONE) {
    sql += 'SELECT ' + out.select;
  }
  sql += ' FROM ' + out.from;
  if (out.where) {
    sql += ' WHERE ' + out.where;
  }
  if (out.group) {
    sql += ' GROUP BY ' + out.group;
  }
  if (out.order) {
    sql += ' ORDER BY ' + out.order;
  }

  return sql;
};


/**
 * @typedef {{
 *   select: string,
 *   from: string,
 *   where: string,
 *   group: string,
 *   order: string
 * }}
 */
ydn.db.schema.Store.SqlParts;


/**
 *
 * @param {!Array} params sql parameter list.
 * @param {ydn.db.base.QueryMethod} method query method.
 * @param {string|undefined} index_column name.
 * @param {ydn.db.KeyRange|IDBKeyRange} key_range to retrieve.
 * @param {boolean} reverse ordering.
 * @param {boolean} unique unique.
 * @return {ydn.db.schema.Store.SqlParts}
 */
ydn.db.schema.Store.prototype.inSql = function(params, method, index_column,
    key_range, reverse, unique) {

  var out = {
    select: '',
    from: '',
    where: '',
    group: '',
    order: ''
  };
  var key_column = this.primary_column_name_;
  var q_key_column = this.primary_column_name_quoted_;
  var index = null;
  if (index_column !== key_column && goog.isString(index_column)) {
    index = this.getIndex(index_column);
  }
  var is_index = !!index;
  var effective_column = index_column || key_column;
  var q_effective_column = goog.string.quote(effective_column);
  var key_path = is_index ? index.getKeyPath() : this.getKeyPath();
  var type = is_index ? index.getType() : this.getType();
  var is_multi_entry = is_index && index.isMultiEntry();

  out.from = this.getQuotedName();
  if (method === ydn.db.base.QueryMethod.COUNT) {
    // primary key is always unqiue.
    out.select = 'COUNT(' + q_key_column + ')';
  } else if (method === ydn.db.base.QueryMethod.LIST_KEYS ||
      method === ydn.db.base.QueryMethod.LIST_KEY ||
      method === ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
    out.select = q_key_column;
    if (goog.isDefAndNotNull(index_column) && index_column != key_column) {
      out.select += ', ' + q_effective_column;
    }
  } else {
    out.select = '*';
  }

  var dist = unique ? 'DISTINCT ' : '';

  var wheres = [];

  if (is_multi_entry) {
    var idx_store_name = goog.string.quote(
        ydn.db.base.PREFIX_MULTIENTRY +
        this.getName() + ':' + index.getName());

    if (method === ydn.db.base.QueryMethod.COUNT) {
      out.select = 'COUNT(' + dist +
          idx_store_name + '.' + q_effective_column + ')';
    } else if (method === ydn.db.base.QueryMethod.LIST_KEYS ||
        method === ydn.db.base.QueryMethod.LIST_KEY ||
        method === ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
      out.select = 'DISTINCT ' + this.getQuotedName() + '.' + q_key_column +
          ', ' + idx_store_name + '.' + q_effective_column +
          ' AS ' + effective_column;
    } else {
      out.select = 'DISTINCT ' + this.getQuotedName() + '.*' +
          ', ' + idx_store_name + '.' + q_effective_column +
          ' AS ' + effective_column;
    }
    out.from = idx_store_name + ' INNER JOIN ' + this.getQuotedName() +
        ' USING (' + q_key_column + ')';

    var col = idx_store_name + '.' + q_effective_column;
    if (goog.isDefAndNotNull(key_range)) {
      ydn.db.KeyRange.toSql(col, type, key_range, wheres, params);
      if (wheres.length > 0) {
        if (out.where) {
          out.where += ' AND ' + wheres.join(' AND ');
        } else {
          out.where = wheres.join(' AND ');
        }
      }
    }
  } else {
    if (goog.isDefAndNotNull(key_range)) {
      ydn.db.KeyRange.toSql(q_effective_column, type, key_range, wheres,
          params);
      if (wheres.length > 0) {
        if (out.where) {
          out.where += ' AND ' + wheres.join(' AND ');
        } else {
          out.where = wheres.join(' AND ');
        }
      }
    }
  }

  if (is_index && !index.isUnique() && unique) {
    out.group = q_effective_column;
  }

  var dir = reverse ? 'DESC' : 'ASC';
  out.order = q_effective_column + ' ' + dir;
  if (is_index) {
    out.order += ', ' + q_key_column + ' ' + dir;
  }

  return out;
};


/**
 * Continue to given effective key position.
 * @param {ydn.db.base.QueryMethod} method query method.
 * @param {!Array.<string>} params sql params.
 * @param {string?} index_name index name.
 * @param {IDBKeyRange|ydn.db.KeyRange} key_range key range.
 * @param {boolean} reverse ordering.
 * @param {boolean} unique unique.
 * @param {IDBKey} key effective key.
 * @param {boolean} open open bound.
 * @return {string} sql.
 */
ydn.db.schema.Store.prototype.sqlContinueEffectiveKey = function(method,
    params, index_name, key_range, reverse, unique, key, open) {
  var p_sql;
  /** @type {IDBKey} */
  var lower;
  /** @type {IDBKey} */
  var upper;
  var lowerOpen, upperOpen;
  if (goog.isDefAndNotNull(key_range)) {
    lower = /** @type {IDBKey} */ (key_range.lower);
    upper = /** @type {IDBKey} */ (key_range.upper);
    lowerOpen = key_range.lowerOpen;
    upperOpen = key_range.upperOpen;

    if (reverse) {
      if (goog.isDefAndNotNull(upper)) {
        var u_cmp = ydn.db.cmp(key, upper);
        if (u_cmp == -1) {
          upper = key;
          upperOpen = open;
        } else if (u_cmp == 0) {
          upperOpen = open || upperOpen;
        }
      } else {
        upper = key;
        upperOpen = open;
      }
    } else {
      if (goog.isDefAndNotNull(lower)) {
        var l_cmp = ydn.db.cmp(key, lower);
        if (l_cmp == 1) {
          lower = key;
          lowerOpen = open;
        } else if (l_cmp == 0) {
          lowerOpen = open || lowerOpen;
        }
      } else {
        lower = key;
        lowerOpen = open;
      }
    }
  } else {
    if (reverse) {
      upper = key;
      upperOpen = open;
    } else {
      lower = key;
      lowerOpen = open;
    }
  }

  key_range = new ydn.db.KeyRange(lower, upper, !!lowerOpen, !!upperOpen);

  var index = index_name ? this.getIndex(index_name) : null;
  var column = index ? index.getSQLIndexColumnName() :
      this.getSQLKeyColumnName();
  var e_sql = this.inSql(params, method,
      column, key_range, reverse, unique);


  var sql = 'SELECT ' + e_sql.select + ' FROM ' + e_sql.from +
      (e_sql.where ? ' WHERE ' + e_sql.where : '') +
      (e_sql.group ? ' GROUP BY ' + e_sql.group : '') +
      ' ORDER BY ' + e_sql.order;

  if (index) {
    var order = reverse ? 'DESC' : 'ASC';
    sql += ', ' + this.getSQLKeyColumnNameQuoted() + order;
  }

  return sql;
};


/**
 * Continue to given effective key position.
 * @param {ydn.db.base.QueryMethod} method query method.
 * @param {!Array.<string>} params sql params.
 * @param {string} index_name index name.
 * @param {IDBKeyRange|ydn.db.KeyRange} key_range key range.
 * @param {IDBKey} key effective key.
 * @param {boolean} open open.
 * @param {IDBKey} primary_key primary key.
 * @param {boolean} reverse ordering.
 * @param {boolean} unique unique.
 * @return {string} sql.
 */
ydn.db.schema.Store.prototype.sqlContinueIndexEffectiveKey = function(method,
    params, index_name, key_range, key, open, primary_key, reverse, unique) {

  var index = this.getIndex(index_name);
  var index_column = index.getSQLIndexColumnName();
  var q_index_column = index.getSQLIndexColumnNameQuoted();
  var primary_column = this.getSQLKeyColumnName();
  var q_primary_column = this.getSQLKeyColumnNameQuoted();

  var op = reverse ? ' <' : ' >';
  if (open) {
    op += ' ';
  } else {
    op += '= ';
  }
  var encode_key = ydn.db.schema.Index.js2sql(key, index.getType());
  var encode_primary_key = ydn.db.schema.Index.js2sql(primary_key,
      this.getType());

  var e_sql;
  var or = '';
  if (key_range) {
    e_sql = this.inSql(params, method,
        index_column, key_range,
        reverse, unique);
    e_sql.where += ' AND ';

    or = q_index_column + op + '?';
    params.push(encode_key);
  } else {
    key_range = reverse ?
        ydn.db.KeyRange.upperBound(key, true) :
        ydn.db.KeyRange.lowerBound(key, true);
    e_sql = this.inSql(params, method,
        index_column, key_range,
        reverse, unique);
    or = e_sql.where;
    e_sql.where = '';
  }

  e_sql.where += '(' + or + ' OR (' + q_index_column + ' = ? AND ' +
      q_primary_column + op + '?))';
  params.push(encode_key);
  params.push(encode_primary_key);

  return 'SELECT ' + e_sql.select + ' FROM ' + e_sql.from +
      ' WHERE ' + e_sql.where +
      (e_sql.group ? ' GROUP BY ' + e_sql.group : '') +
      ' ORDER BY ' + e_sql.order;
};


/**
 *
 * @return {!ydn.db.schema.Store} clone this database schema.
 */
ydn.db.schema.Store.prototype.clone = function() {
  return ydn.db.schema.Store.fromJSON(
      /** @type {!StoreSchema} */ (this.toJSON()));
};


/**
 *
 * @return {number}
 */
ydn.db.schema.Store.prototype.countIndex = function() {
  return this.indexes.length;
};


/**
 *
 * @param {number} idx index of index.
 * @return {ydn.db.schema.Index}
 */
ydn.db.schema.Store.prototype.index = function(idx) {
  return this.indexes[idx] || null;
};


/**
 *
 * @param {string} name index name.
 * @return {ydn.db.schema.Index} index if found.
 */
ydn.db.schema.Store.prototype.getIndex = function(name) {
  return /** @type {ydn.db.schema.Index} */ (goog.array.find(this.indexes,
      function(x) {
        return x.getName() == name;
      }));
};


/**
 * Query index from index key path.
 * @param {string|!Array.<string>} key_path key path.
 * @return {ydn.db.schema.Index} resulting index.
 */
ydn.db.schema.Store.prototype.getIndexByKeyPath = function(key_path) {
  for (var i = 0; i < this.indexes.length; i++) {
    if (this.indexes[i].equalsKeyPath(key_path)) {
      return this.indexes[i];
    }
  }
  return null;
};


/**
 * @return {boolean} return true if store is fixed.
 */
ydn.db.schema.Store.prototype.isFixed = function() {
  return this.fixed;
};


/**
 * @return {boolean} return true if store is encrypted.
 */
ydn.db.schema.Store.prototype.isEncrypted = function() {
  return this.is_encrypted_;
};


/**
 * @see #hasIndexByKeyPath
 * @param {string} name index name.
 * @return {boolean} return true if name is found in the index or primary
 * keyPath.
 */
ydn.db.schema.Store.prototype.hasIndex = function(name) {
  if (name === this.keyPath) {
    return true;
  }

  return goog.array.some(this.indexes, function(x) {
    return x.getName() == name;
  });
};


/**
 * Check given key_path is equals to store key path.
 * @param {(string|goog.array.ArrayLike)=} opt_key_path
 * @return {boolean}
 */
ydn.db.schema.Store.prototype.isKeyPath = function(opt_key_path) {
  if (goog.isDef(this.keyPath)) {
    if (this.keyPaths.length == 1) {
      return this.keyPath === opt_key_path;
    } else if (goog.isArrayLike(opt_key_path)) {
      return goog.array.equals(this.keyPaths,
          /** @type {goog.array.ArrayLike} */ (opt_key_path));
    } else {
      return false;
    }
  } else {
    return false;
  }
};


/**
 * @see #hasIndex
 * @param {string|!Array.<string>} key_path index key path.
 * @return {boolean} return true if key_path is found in the index including
 * primary keyPath.
 */
ydn.db.schema.Store.prototype.hasIndexByKeyPath = function(key_path) {
  if (this.keyPath &&
      goog.isNull(ydn.db.schema.Index.compareKeyPath(this.keyPath, key_path))) {
    return true;
  }
  return goog.array.some(this.indexes, function(x) {
    return goog.isDefAndNotNull(x.keyPath) &&
        goog.isNull(ydn.db.schema.Index.compareKeyPath(x.keyPath, key_path));
  });
};


/**
 * Return quoted keyPath. In case undefined return default key column.
 * @return {string} return quoted keyPath. If keyPath is array, they are
 * join by ',' and quoted. If keyPath is not define, default sqlite column
 * name is used.
 */
ydn.db.schema.Store.prototype.getSQLKeyColumnNameQuoted = function() {
  return this.primary_column_name_quoted_;
};


/**
 * Return quoted keyPath. In case undefined return default key column.
 * @return {string} return quoted keyPath. If keyPath is array, they are
 * join by ',' and quoted. If keyPath is not define, default sqlite column
 * name is used.
 */
ydn.db.schema.Store.prototype.getSQLKeyColumnName = function() {
  return this.primary_column_name_;
};


/**
 * @type {string}
 * @private
 */
ydn.db.schema.Store.prototype.primary_column_name_;


/**
 * @type {string}
 * @private
 */
ydn.db.schema.Store.prototype.primary_column_name_quoted_;


/**
 *
 * @return {string} return quoted name.
 */
ydn.db.schema.Store.prototype.getQuotedName = function() {
  return goog.string.quote(this.name_);
};


/**
 * @return {Array.<string>} return name of indexed. It is used as column name
 * in WebSql.
 */
ydn.db.schema.Store.prototype.getColumns = function() {
  if (this.columns_ && this.columns_.length != this.indexes.length) {
    /**
     * @private
     * @final
     * @type {Array.<string>}
     */
    this.columns_ = [];
    for (var i = 0; i < this.indexes.length; i++) {
      this.columns_.push(this.indexes[i].getName());
    }
  }
  return this.columns_;
};


/**
 * Update store schema with given guided store schema for
 * indexeddb.
 * these include:
 *   1. blob column data type
 * @param {!ydn.db.schema.Store} that guided store schema.
 */
ydn.db.schema.Store.prototype.hintForIdb = function(that) {
  for (var i = 0; i < that.indexes.length; i++) {
    var index = that.indexes[i];
    if (!this.hasIndex(index.getName()) &&
        index.getType() == ydn.db.schema.DataType.BLOB) {
      var clone = new ydn.db.schema.Index(
          index.getKeyPath(), index.getType(), index.isUnique(),
          index.isMultiEntry(), index.getName());
      this.indexes.push(clone);
    }
  }
};


/**
 * Create a new update store schema with given guided store schema.
 * NOTE: This is used in websql for checking table schema sniffed from the
 * connection is similar to requested table schema. The fact is that
 * some schema information are not able to reconstruct from the connection,
 * these include:
 *   1. composite index: in which a composite index is blown up to multiple
 *     columns. @see ydn.db.con.WebSql.prototype.prepareTableSchema_.
 * @param {ydn.db.schema.Store} that guided store schema.
 * @return {!ydn.db.schema.Store} updated store schema.
 */
ydn.db.schema.Store.prototype.hintForWebSql = function(that) {
  if (!that) {
    return this;
  }
  goog.asserts.assert(this.name_ == that.name_, 'store name: ' +
      this.name_ + ' != ' + that.name_);
  var autoIncrement = this.autoIncrement;
  var keyPath = goog.isArray(this.keyPath) ?
      goog.array.clone(/** @type {goog.array.ArrayLike} */ (this.keyPath)) :
      this.keyPath;
  var type = this.type;
  var indexes = goog.array.map(this.indexes, function(index) {
    return index.clone();
  });
  if (!goog.isDef(that.type) && type == 'TEXT') {
    // composite are converted into TEXT
    type = undefined;
  }
  if (goog.isArray(that.keyPath) && goog.isString(keyPath) &&
      keyPath == that.keyPath.join(',')) {
    keyPath = goog.array.clone(
        /** @type {goog.array.ArrayLike} */ (that.keyPath));
  }

  // update composite index
  for (var i = 0, n = that.indexes.length; i < n; i++) {
    if (that.indexes[i].isComposite()) {
      var name = that.indexes[i].getName();
      for (var j = indexes.length - 1; j >= 0; j--) {
        if (name.indexOf(indexes[j].getName()) >= 0) {
          indexes[j] = that.indexes[i].clone();
          break;
        }
      }
    }
  }

  for (var i = 0; i < indexes.length; i++) {
    var that_index = that.getIndex(indexes[i].getName());
    if (that_index) {
      indexes[i] = indexes[i].hint(that_index);
    }
  }

  return new ydn.db.schema.Store(
      that.name_, keyPath, autoIncrement, type, indexes);
};


/**
 *
 * @return {string} store name.
 */
ydn.db.schema.Store.prototype.getName = function() {
  return this.name_;
};


/**
 *
 * @return {boolean|undefined} autoIncrement.
 */
ydn.db.schema.Store.prototype.isAutoIncrement = function() {
  return this.autoIncrement;
};


/**
 *
 * @return {Array.<string>|string} keyPath.
 */
ydn.db.schema.Store.prototype.getKeyPath = function() {
  return this.keyPath;
};


/**
 *
 * @return {boolean} true if inline key is in used.
 */
ydn.db.schema.Store.prototype.usedInlineKey = function() {
  return !!this.keyPath;
};


/**
 *
 * @return {!Array.<string>} list of index names.
 */
ydn.db.schema.Store.prototype.getIndexNames = function() {
  return this.indexes.map(function(x) {return x.getName();});
};


/**
 *
 * @return {ydn.db.schema.DataType|undefined}
 */
ydn.db.schema.Store.prototype.getType = function() {
  return this.type;
};


/**
 *
 * @return {ydn.db.schema.DataType}
 */
ydn.db.schema.Store.prototype.getSqlType = function() {
  return this.keyColumnType_;
};


/**
 *
 * @return {!Array.<string>} list of index keyPath.
 */
ydn.db.schema.Store.prototype.getIndexKeyPaths = function() {
  return this.indexes.map(function(x) {return x.keyPath;});
};


/**
 *
 * @param {string} name column name or keyPath.
 * @param {ydn.db.schema.DataType=} opt_type optional column data type.
 * @param {boolean=} opt_unique unique.
 * @param {boolean=} opt_multiEntry true for array index to index individual
 * element.
 */
ydn.db.schema.Store.prototype.addIndex = function(name, opt_type, opt_unique,
                                                  opt_multiEntry) {
  this.indexes.push(new ydn.db.schema.Index(name, opt_type, opt_unique,
      opt_multiEntry));
};


/**
 * Extract primary key value of keyPath from a given object.
 * @param {Object} record record value.
 * @param {IDBKey=} opt_key out-of-line key.
 * @return {!IDBKey|undefined} extracted primary key.
 */
ydn.db.schema.Store.prototype.extractKey = function(record, opt_key) {
  if (!record) {
    return undefined;
  }
  if (!this.usedInlineKey() && goog.isDefAndNotNull(opt_key)) {
    return opt_key;
  }
  // http://www.w3.org/TR/IndexedDB/#key-construct
  if (this.isComposite) {
    var arr = [];
    for (var i = 0; i < this.keyPath.length; i++) {
      arr.push(ydn.db.utils.getValueByKeys(record, this.keyPath[i]));
    }
    return arr;
  } else if (this.keyPath) {
    return /** @type {!IDBKey} */ (goog.object.getValueByKeys(
        record, this.keyPaths));
  } else {
    return undefined;
  }
};


/**
 * Extract value of keyPath from a row of SQL results
 * @param {!Object} obj record value.
 * @return {!Array|number|string|undefined} return key value.
 */
ydn.db.schema.Store.prototype.getRowValue = function(obj) {
  if (goog.isDefAndNotNull(this.keyPath)) {
    var value = obj[this.keyPath];
    if (this.type == ydn.db.schema.DataType.DATE) {
      value = Date.parse(value);
    } else if (this.type == ydn.db.schema.DataType.NUMERIC) {
      value = parseFloat(value);
    } else if (this.type == ydn.db.schema.DataType.INTEGER) {
      value = parseInt(value, 10);
    }
    return value;
  } else {
    return undefined;
  }
};


/**
 * Generated a key starting from 0 with increment of 1.
 * NOTE: Use only by simple store.
 * @return {number} generated key.
 */
ydn.db.schema.Store.prototype.generateKey = function() {
  if (!goog.isDef(this.current_key_)) {

    /**
     * @type {number}
     * @private
     */
    this.current_key_ = 0;
  }
  return this.current_key_++;
};


/**
 * Set keyPath field of the object with given value.
 * @see #getKeyValue
 * @param {!Object} obj get key value from its keyPath field.
 * @param {*} value key value to set.
 */
ydn.db.schema.Store.prototype.setKeyValue = function(obj, value) {

  for (var i = 0; i < this.keyPaths.length; i++) {
    var key = this.keyPaths[i];

    if (i == this.keyPaths.length - 1) {
      obj[key] = value;
      return;
    }

    if (!goog.isDef(obj[key])) {
      obj[key] = {};
    }
    obj = obj[key];
  }
};


/**
 * Prepare SQL column name and values.
 * @param {!Object} obj get values of indexed fields.
 * @param {IDBKey=} opt_key optional key.
 * @param {boolean=} opt_exclude_unique_column exclude unique constrained
 * columns.
 * @return {{
 *    columns: Array.<string>,
 *    slots: Array.<string>,
 *    values: Array.<string>,
 *    key: (IDBKey|undefined)
 *  }} return list of values as it appear on the indexed fields.
 */
ydn.db.schema.Store.prototype.sqlNamesValues = function(obj, opt_key,
    opt_exclude_unique_column) {

  // since corretness of the inline, offline, auto are already checked,
  // here we don't check again. this method should not throw error for
  // these reason. If error must be throw it has to be InternalError.

  var values = [];
  var columns = [];

  var key = goog.isDef(opt_key) ? opt_key : this.extractKey(obj);
  if (goog.isDef(key)) {
    columns.push(this.getSQLKeyColumnNameQuoted());
    values.push(ydn.db.schema.Index.js2sql(key, this.getType()));
  }

  for (var i = 0; i < this.indexes.length; i++) {
    var index = this.indexes[i];
    if (index.isMultiEntry() ||
        index.getName() === this.keyPath ||
        index.getName() == ydn.db.base.DEFAULT_BLOB_COLUMN ||
        (!!opt_exclude_unique_column && index.isUnique())) {
      continue;
    }

    var idx_key = index.extractKey(obj);
    if (goog.isDefAndNotNull(idx_key)) {
      values.push(ydn.db.schema.Index.js2sql(idx_key, index.getType()));
      columns.push(index.getSQLIndexColumnNameQuoted());
    }
  }

  if (!this.fixed) {
    values.push(ydn.json.stringify(obj));
    columns.push(ydn.db.base.DEFAULT_BLOB_COLUMN);
  } else if (this.isFixed() && !this.usedInlineKey() &&
      this.countIndex() == 0) {
    // check for blob
    var BASE64_MARKER = ';base64,';
    if (goog.isString(obj) && obj.indexOf(BASE64_MARKER) == -1) {
      values.push(obj);
      columns.push(ydn.db.base.DEFAULT_BLOB_COLUMN);
    } else {
      values.push(ydn.json.stringify(obj));
      columns.push(ydn.db.base.DEFAULT_BLOB_COLUMN);
    }
  }

  var slots = [];
  for (var i = values.length - 1; i >= 0; i--) {
    slots[i] = '?';
  }

  return {
    columns: columns,
    slots: slots,
    values: values,
    key: key
  };
};


/**
 * Compare two stores.
 * @see #similar
 * @param {ydn.db.schema.Store} store store schema to test.
 * @return {boolean} true if store schema is exactly equal to this schema.
 */
ydn.db.schema.Store.prototype.equals = function(store) {
  return this.name_ === store.name_ &&
      ydn.object.equals(this.toJSON(), store.toJSON());
};


/**
 * Compare two stores.
 * @see #equals
 * @param {ydn.db.schema.Store} store
 * @return {string} explination for difference, empty string for similar.
 */
ydn.db.schema.Store.prototype.difference = function(store) {

  if (!store) {
    return 'missing store: ' + this.name_;
  }
  if (this.name_ != store.name_) {
    return 'store name, expect: ' + this.name_ + ', but: ' + store.name_;
  }
  var msg = ydn.db.schema.Index.compareKeyPath(this.keyPath, store.keyPath);
  if (msg) {
    return 'keyPath, ' + msg;
  }
  if (goog.isDef(this.autoIncrement) && goog.isDef(store.autoIncrement) &&
      this.autoIncrement != store.autoIncrement) {
    return 'autoIncrement, expect:  ' + this.autoIncrement + ', but: ' +
        store.autoIncrement;
  }
  if (this.indexes.length != store.indexes.length) {
    return 'indexes length, expect:  ' + this.indexes.length + ', but: ' +
        store.indexes.length;
  }

  if (goog.isDef(this.type) && goog.isDef(store.type) &&
      (goog.isArrayLike(this.type) ? !goog.array.equals(
      /** @type {goog.array.ArrayLike} */ (this.type),
      /** @type {goog.array.ArrayLike} */ (store.type)) :
      this.type != store.type)) {
    return 'data type, expect:  ' + this.type + ', but: ' + store.type;
  }
  for (var i = 0; i < this.indexes.length; i++) {
    var index = store.getIndex(this.indexes[i].getName());
    var index_msg = this.indexes[i].difference(index);
    if (index_msg.length > 0) {
      return 'index "' + this.indexes[i].getName() + '" ' + index_msg;
    }
  }

  return '';
};


/**
 *
 * @param {ydn.db.schema.Store} store schema.
 * @return {boolean} true if given store schema is similar to this.
 */
ydn.db.schema.Store.prototype.similar = function(store) {
  return this.difference(store).length == 0;
};


/**
 * @type {Object.<Function>} index generator function for each index.
 */
ydn.db.schema.Store.prototype.index_generators;


/**
 * Add index by generator.
 * @param {Object} obj record value.
 */
ydn.db.schema.Store.prototype.generateIndex = function(obj) {
  if (!obj) {
    return;
  }
  for (var i = 0; i < this.indexes.length; i++) {
    this.indexes[i].generateIndex(obj);
  }
};


/**
 * @param {function(!ydn.db.Request, goog.array.ArrayLike)} hook database
 * pre-hook function.
 * @return {number} internal hook index.
 */
ydn.db.schema.Store.prototype.addHook = function(hook) {
  this.hooks_.push(hook);
  return this.hooks_.length - 1;
};


/**
 * Invoke hook functions.
 * Database hook to call before persisting into the database.
 * Override this function to attach the hook. The default implementation is
 * immediately invoke the given callback with first variable argument.
 * to preserve database operation order, preHook call is not waited.
 * @param {!ydn.db.Request} df deferred from database operation.
 * @param {goog.array.ArrayLike} args arguments to the db method.
 * @param {number=} opt_hook_idx hook index to ignore.
 * @final
 */
ydn.db.schema.Store.prototype.hook = function(df, args, opt_hook_idx) {
  for (var i = 0; i < this.hooks_.length; i++) {
    if (opt_hook_idx !== i) {
      this.hooks_[i](df, args);
    }
  }
};


/**
 * Lookup index from the schema.
 * @param {!Array.<string>|string} index_name_or_key_path index name or
 * key path.
 * @return {string} index name.
 */
ydn.db.schema.Store.prototype.getIndexName = function(index_name_or_key_path) {

  var index;
  var index_name = index_name_or_key_path;
  if (goog.isArray(index_name_or_key_path)) {
    index = this.getIndexByKeyPath(index_name_or_key_path);
    index_name = index_name_or_key_path.join(', ');
  } else {
    index = this.getIndex(index_name_or_key_path);
  }
  if (goog.DEBUG && !index) {
    throw new ydn.debug.error.ArgumentException('require index "' +
        index_name + '" not found in store "' + this.getName() + '"');
  }
  return index.getName();
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.schema.Store.prototype.toString = function() {
    return 'Store:' + this.name_ + '[' + this.countIndex() + 'index]';
  };
}


// Input 58


goog.provide('ydn.db.schema.fulltext.Entry');



/**
 * @interface
 */
ydn.db.schema.fulltext.Entry = function() {};





// Input 59
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Interface for full text serach engine.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.schema.fulltext.Engine');
goog.provide('ydn.db.schema.fulltext.ResultSet');
goog.require('ydn.db.schema.fulltext.Entry');



/**
 * @interface
 */
ydn.db.schema.fulltext.ResultSet = function() {};


/**
 * Get list of store name involved in this catalog.
 * @return {!Array.<string>}
 */
ydn.db.schema.fulltext.ResultSet.prototype.getStoreList = function() {};


/**
 * Next database lookup.
 * @param {function(string, string, ydn.db.KeyRange,
 * ydn.db.schema.fulltext.Entry)} cb callback for next query.
 */
ydn.db.schema.fulltext.ResultSet.prototype.nextLookup = function(cb) {};


/**
 * Return result from lookup.
 * @param {ydn.db.schema.fulltext.Entry} query
 * @param {Array} results
 * @return {boolean?} return true to invoke next looup, false when done and
 * null if not done.
 */
ydn.db.schema.fulltext.ResultSet.prototype.addResult =
    function(query, results) {};


/**
 * @return {Array.<ydn.db.schema.fulltext.Entry>}
 */
ydn.db.schema.fulltext.ResultSet.prototype.collect = function() {};



/**
 * @interface
 */
ydn.db.schema.fulltext.Engine = function() {};


/**
 * Free text query.
 * @param {string} catalog_name
 * @param {string} query
 * @param {number=} opt_limit
 * @param {number=} opt_threshold
 * @return {ydn.db.schema.fulltext.ResultSet}
 */
ydn.db.schema.fulltext.Engine.prototype.query = function(catalog_name, query,
    opt_limit, opt_threshold) {};


/**
 * Analyze an indexing value.
 * @param {string} store_name the store name in which document belong.
 * @param {IDBKey} key primary of the document.
 * @param {!Object} obj the document to be indexed.
 * @return {Array.<ydn.db.schema.fulltext.Entry>} score for each token.
 */
ydn.db.schema.fulltext.Engine.prototype.analyze = function(
    store_name, key, obj) {};

// Input 60
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview JSON utility functions.
 * @author arv@google.com (Erik Arvidsson)
 */


goog.provide('goog.json');
goog.provide('goog.json.Serializer');


/**
 * Tests if a string is an invalid JSON string. This only ensures that we are
 * not using any invalid characters
 * @param {string} s The string to test.
 * @return {boolean} True if the input is a valid JSON string.
 * @private
 */
goog.json.isValid_ = function(s) {
  // All empty whitespace is not valid.
  if (/^\s*$/.test(s)) {
    return false;
  }

  // This is taken from http://www.json.org/json2.js which is released to the
  // public domain.
  // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
  // inside strings.  We also treat \u2028 and \u2029 as whitespace which they
  // are in the RFC but IE and Safari does not match \s to these so we need to
  // include them in the reg exps in all places where whitespace is allowed.
  // We allowed \x7f inside strings because some tools don't escape it,
  // e.g. http://www.json.org/java/org/json/JSONObject.java

  // Parsing happens in three stages. In the first stage, we run the text
  // against regular expressions that look for non-JSON patterns. We are
  // especially concerned with '()' and 'new' because they can cause invocation,
  // and '=' because it can cause mutation. But just to be safe, we want to
  // reject all unexpected forms.

  // We split the first stage into 4 regexp operations in order to work around
  // crippling inefficiencies in IE's and Safari's regexp engines. First we
  // replace all backslash pairs with '@' (a non-JSON character). Second, we
  // replace all simple value tokens with ']' characters. Third, we delete all
  // open brackets that follow a colon or comma or that begin the text. Finally,
  // we look to see that the remaining characters are only whitespace or ']' or
  // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

  // Don't make these static since they have the global flag.
  var backslashesRe = /\\["\\\/bfnrtu]/g;
  var simpleValuesRe =
      /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
  var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
  var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;

  return remainderRe.test(s.replace(backslashesRe, '@').
      replace(simpleValuesRe, ']').
      replace(openBracketsRe, ''));
};


/**
 * Parses a JSON string and returns the result. This throws an exception if
 * the string is an invalid JSON string.
 *
 * Note that this is very slow on large strings. If you trust the source of
 * the string then you should use unsafeParse instead.
 *
 * @param {*} s The JSON string to parse.
 * @return {Object} The object generated from the JSON string.
 */
goog.json.parse = function(s) {
  var o = String(s);
  if (goog.json.isValid_(o)) {
    /** @preserveTry */
    try {
      return /** @type {Object} */ (eval('(' + o + ')'));
    } catch (ex) {
    }
  }
  throw Error('Invalid JSON string: ' + o);
};


/**
 * Parses a JSON string and returns the result. This uses eval so it is open
 * to security issues and it should only be used if you trust the source.
 *
 * @param {string} s The JSON string to parse.
 * @return {Object} The object generated from the JSON string.
 */
goog.json.unsafeParse = function(s) {
  return /** @type {Object} */ (eval('(' + s + ')'));
};


/**
 * JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
 *
 * TODO(nicksantos): Array should also be a valid replacer.
 *
 * @typedef {function(this:Object, string, *): *}
 */
goog.json.Replacer;


/**
 * JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
 *
 * @typedef {function(this:Object, string, *): *}
 */
goog.json.Reviver;


/**
 * Serializes an object or a value to a JSON string.
 *
 * @param {*} object The object to serialize.
 * @param {?goog.json.Replacer=} opt_replacer A replacer function
 *     called for each (key, value) pair that determines how the value
 *     should be serialized. By defult, this just returns the value
 *     and allows default serialization to kick in.
 * @throws Error if there are loops in the object graph.
 * @return {string} A JSON string representation of the input.
 */
goog.json.serialize = function(object, opt_replacer) {
  // NOTE(nicksantos): Currently, we never use JSON.stringify.
  //
  // The last time I evaluated this, JSON.stringify had subtle bugs and behavior
  // differences on all browsers, and the performance win was not large enough
  // to justify all the issues. This may change in the future as browser
  // implementations get better.
  //
  // assertSerialize in json_test contains if branches for the cases
  // that fail.
  return new goog.json.Serializer(opt_replacer).serialize(object);
};



/**
 * Class that is used to serialize JSON objects to a string.
 * @param {?goog.json.Replacer=} opt_replacer Replacer.
 * @constructor
 */
goog.json.Serializer = function(opt_replacer) {
  /**
   * @type {goog.json.Replacer|null|undefined}
   * @private
   */
  this.replacer_ = opt_replacer;
};


/**
 * Serializes an object or a value to a JSON string.
 *
 * @param {*} object The object to serialize.
 * @throws Error if there are loops in the object graph.
 * @return {string} A JSON string representation of the input.
 */
goog.json.Serializer.prototype.serialize = function(object) {
  var sb = [];
  this.serialize_(object, sb);
  return sb.join('');
};


/**
 * Serializes a generic value to a JSON string
 * @private
 * @param {*} object The object to serialize.
 * @param {Array} sb Array used as a string builder.
 * @throws Error if there are loops in the object graph.
 */
goog.json.Serializer.prototype.serialize_ = function(object, sb) {
  switch (typeof object) {
    case 'string':
      this.serializeString_(/** @type {string} */ (object), sb);
      break;
    case 'number':
      this.serializeNumber_(/** @type {number} */ (object), sb);
      break;
    case 'boolean':
      sb.push(object);
      break;
    case 'undefined':
      sb.push('null');
      break;
    case 'object':
      if (object == null) {
        sb.push('null');
        break;
      }
      if (goog.isArray(object)) {
        this.serializeArray(/** @type {!Array} */ (object), sb);
        break;
      }
      // should we allow new String, new Number and new Boolean to be treated
      // as string, number and boolean? Most implementations do not and the
      // need is not very big
      this.serializeObject_(/** @type {Object} */ (object), sb);
      break;
    case 'function':
      // Skip functions.
      // TODO(user) Should we return something here?
      break;
    default:
      throw Error('Unknown type: ' + typeof object);
  }
};


/**
 * Character mappings used internally for goog.string.quote
 * @private
 * @type {Object}
 */
goog.json.Serializer.charToJsonCharCache_ = {
  '\"': '\\"',
  '\\': '\\\\',
  '/': '\\/',
  '\b': '\\b',
  '\f': '\\f',
  '\n': '\\n',
  '\r': '\\r',
  '\t': '\\t',

  '\x0B': '\\u000b' // '\v' is not supported in JScript
};


/**
 * Regular expression used to match characters that need to be replaced.
 * The S60 browser has a bug where unicode characters are not matched by
 * regular expressions. The condition below detects such behaviour and
 * adjusts the regular expression accordingly.
 * @private
 * @type {RegExp}
 */
goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
    /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;


/**
 * Serializes a string to a JSON string
 * @private
 * @param {string} s The string to serialize.
 * @param {Array} sb Array used as a string builder.
 */
goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
  // The official JSON implementation does not work with international
  // characters.
  sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
    // caching the result improves performance by a factor 2-3
    if (c in goog.json.Serializer.charToJsonCharCache_) {
      return goog.json.Serializer.charToJsonCharCache_[c];
    }

    var cc = c.charCodeAt(0);
    var rv = '\\u';
    if (cc < 16) {
      rv += '000';
    } else if (cc < 256) {
      rv += '00';
    } else if (cc < 4096) { // \u1000
      rv += '0';
    }
    return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
  }), '"');
};


/**
 * Serializes a number to a JSON string
 * @private
 * @param {number} n The number to serialize.
 * @param {Array} sb Array used as a string builder.
 */
goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
  sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
};


/**
 * Serializes an array to a JSON string
 * @param {Array} arr The array to serialize.
 * @param {Array} sb Array used as a string builder.
 * @protected
 */
goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
  var l = arr.length;
  sb.push('[');
  var sep = '';
  for (var i = 0; i < l; i++) {
    sb.push(sep);

    var value = arr[i];
    this.serialize_(
        this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
        sb);

    sep = ',';
  }
  sb.push(']');
};


/**
 * Serializes an object to a JSON string
 * @private
 * @param {Object} obj The object to serialize.
 * @param {Array} sb Array used as a string builder.
 */
goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
  sb.push('{');
  var sep = '';
  for (var key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      var value = obj[key];
      // Skip functions.
      // TODO(ptucker) Should we return something for function properties?
      if (typeof value != 'function') {
        sb.push(sep);
        this.serializeString_(key, sb);
        sb.push(':');

        this.serialize_(
            this.replacer_ ? this.replacer_.call(obj, key, value) : value,
            sb);

        sep = ',';
      }
    }
  }
  sb.push('}');
};

// Input 61
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Robust JSON wrapper.
 */

goog.provide('ydn.json');
goog.require('goog.debug.Logger');
goog.require('goog.json');


/**
 *
 * @define {boolean} enable debug to log erronous object.
 */
ydn.json.DEBUG = false;


/**
 * @define {boolean} Use JSON polyfill.
 */
ydn.json.POLY_FILL = false;


/**
 * @final
 * @type {goog.debug.Logger}
 */
ydn.json.logger = goog.debug.Logger.getLogger('ydn');


/**
 * Parse JSON using native method if available.
 * This is necessary since closure-library do not use native method.
 *
 * @param {*} json_str string to parse.
 * @return {!Object} parse return object.
 */
ydn.json.parse = function(json_str) {
  if (!goog.isString(json_str) || goog.string.isEmpty(json_str)) {
    return {};
  }
  if (ydn.json.POLY_FILL && (typeof goog.global['JSON'] == 'undefined')) {
    return /** @type {!Object} */ (goog.json.unsafeParse(json_str));
  } else {
    return /** @type {!Object} */ (JSON.parse(json_str));
  }
};


/**
 * Note: This is mainly used in debugging.
 * @param obj
 * @return {string}
 */
ydn.json.toShortString = function(obj) {
  var json;
  /** @preserveTry */
  try {
    json = ydn.json.stringify(obj);
  } catch (e) {
    json = '';
  }
  if (json) {
    return json.substr(0, 70) + (json.length > 70 ? '...' : '');
  } else {
    return '';
  }

};


/**
 * Parse JSON using native method if available.
 *
 * This is necessary since closure-library do not use native method.
 *
 * @param {Object} json object to stringify.
 * @param {Function=} opt_replacer If a function, transforms values and properties
 * encountered while stringifying.
 * @param {number=} opt_space Causes the resulting string to be pretty-printed.
 * @return {string} result.
 */
ydn.json.stringify = function(json, opt_replacer, opt_space) {

  if (ydn.json.POLY_FILL && (typeof goog.global['JSON'] == 'undefined')) {
    return goog.json.serialize(json, opt_replacer);
  } else {
    return JSON.stringify(json, opt_replacer, opt_space);
  }
};

// Input 62
// Copyright 2013 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Inverted index.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.schema.fulltext.InvIndex');
goog.require('goog.array');
goog.require('ydn.debug.error.ArgumentException');
goog.require('ydn.json');



/**
 * Primary index for fulltext search index.
 * @param {string} store_name store name of which index reside.
 * @param {string} key_path the index name.
 * @param {number?=} opt_weight index weight. Default to 1.
 * @constructor
 * @struct
 */
ydn.db.schema.fulltext.InvIndex = function(store_name, key_path,
                                           opt_weight) {
  if (goog.DEBUG) {
    if (!store_name || goog.string.isEmpty(store_name)) {
      throw new ydn.debug.error.ArgumentException('store_name must be' +
          ' provided for primary full text index');
    }
    if (!key_path || goog.string.isEmpty(key_path)) {
      throw new ydn.debug.error.ArgumentException('index_name must be' +
          ' provided for primary full text index');
    }
  }
  /**
   * @protected
   * @type {string}
   */
  this.store_name = store_name;
  /**
   * @protected
   * @type {string}
   */
  this.key_path = key_path;
  /**
   * @protected
   * @type {number}
   */
  this.weight = opt_weight || 1.0;
};


/**
 * @return {string}
 */
ydn.db.schema.fulltext.InvIndex.prototype.getStoreName = function() {
  return this.store_name;
};


/**
 * @return {number}
 */
ydn.db.schema.fulltext.InvIndex.prototype.getWeight = function() {
  return this.weight;
};


/**
 * @return {string}
 */
ydn.db.schema.fulltext.InvIndex.prototype.getKeyPath = function() {
  return this.key_path;
};


/**
 * @param {InvIndex} json
 * @return {!ydn.db.schema.fulltext.InvIndex}
 */
ydn.db.schema.fulltext.InvIndex.fromJson = function(json) {
  if (goog.DEBUG) {
    var fields = ['storeName', 'keyPath', 'weight'];
    for (var key in json) {
      if (json.hasOwnProperty(key) && goog.array.indexOf(fields, key) == -1) {
        throw new ydn.debug.error.ArgumentException('Unknown field: ' + key +
            ' in ' + ydn.json.toShortString(json));
      }
    }
  }
  return new ydn.db.schema.fulltext.InvIndex(json.storeName, json.keyPath,
      json.weight);
};


// Input 63
// Copyright 2013 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Fulltext index.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.schema.fulltext.Catalog');
goog.require('goog.array');
goog.require('ydn.db.schema.fulltext.Engine');
goog.require('ydn.db.schema.fulltext.InvIndex');
goog.require('ydn.debug.error.ArgumentException');



/**
 * Catalog of full text search indexes.
 * @param {string} name fulltext search index name.
 * @param {Array.<ydn.db.schema.fulltext.InvIndex>} indexes list of primary
 * index, in which indexes are stored.
 * @param {string=} opt_lang language.
 * @param {Array.<string>=} opt_normalizers list of normalizer.
 * @constructor
 * @struct
 */
ydn.db.schema.fulltext.Catalog = function(name, indexes, opt_lang,
                                          opt_normalizers) {
  /**
   * @protected
   * @type {string}
   */
  this.name = name;
  /**
   * @protected
   * @type {Array.<ydn.db.schema.fulltext.InvIndex>}
   */
  this.indexes = indexes;
  /**
   * @final
   * @type {string}
   */
  this.lang = opt_lang || '';
  if (goog.DEBUG) {
    if (['', 'en', 'fr'].indexOf(this.lang) == -1) {
      throw new ydn.debug.error.ArgumentException('Unsupported lang "' +
          opt_lang + ' for full text search index ' + name);
    }
  }
  /**
   * @final
   * @type {Array.<string>}
   */
  this.normalizers = opt_normalizers || null;
  /**
   * @type {ydn.db.schema.fulltext.Engine}
   */
  this.engine = null;
};


/**
 * Return unique source store name.
 * @return {!Array.<string>}
 */
ydn.db.schema.fulltext.Catalog.prototype.getSourceNames = function() {
  var arr = [];
  for (var i = 0; i < this.indexes.length; i++) {
    var name = this.indexes[i].getStoreName();
    if (arr.indexOf(name) == -1) {
      arr.push(name);
    }
  }
  return arr;
};


/**
 * @return {string} full text index name. This is store name as well.
 */
ydn.db.schema.fulltext.Catalog.prototype.getName = function() {
  return this.name;
};


/**
 * @return {number} number of primary indexes.
 */
ydn.db.schema.fulltext.Catalog.prototype.count = function() {
  return this.indexes.length;
};


/**
 * @param {number} idx index of indexes.
 * @return {ydn.db.schema.fulltext.InvIndex} Index at idx.
 */
ydn.db.schema.fulltext.Catalog.prototype.index = function(idx) {
  return this.indexes[idx];
};


/**
 * @param {string} store_name store name.
 * @param {string} index_name store name.
 * @return {ydn.db.schema.fulltext.InvIndex} Index at idx.
 */
ydn.db.schema.fulltext.Catalog.prototype.getSource = function(store_name,
                                                              index_name) {
  return goog.array.find(this.indexes, function(x) {
    return x.getStoreName() == store_name && x.getKeyPath() == index_name;
  });
};


/**
 * @param {FullTextCatalog} json
 * @return {!ydn.db.schema.fulltext.Catalog}
 */
ydn.db.schema.fulltext.Catalog.fromJson = function(json) {
  if (goog.DEBUG) {
    var fields = ['name', 'sources', 'lang'];
    for (var key in json) {
      if (json.hasOwnProperty(key) && goog.array.indexOf(fields, key) == -1) {
        throw new ydn.debug.error.ArgumentException('Unknown field: ' + key +
            ' in ' + ydn.json.stringify(json));
      }
    }
  }
  if (!goog.isArray(json.sources)) {
    throw new ydn.debug.error.ArgumentException('indexes require for ' +
        'full text search index ' + json.name + ', but ' + json.sources +
        ' of type ' + typeof json.sources + ' found.');
  }
  var indexes = json.sources.map(function(x) {
    return ydn.db.schema.fulltext.InvIndex.fromJson(x);
  });
  return new ydn.db.schema.fulltext.Catalog(json.name, indexes, json.lang);
};


// Input 64
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Database schema.
 *
 * This data structure is immutable.
 */



goog.provide('ydn.db.schema.Database');
goog.require('ydn.db.Key');
goog.require('ydn.db.schema.Store');
goog.require('ydn.db.schema.fulltext.Catalog');



/**
 *
 * @param {DatabaseSchema|number|string=} opt_version version, if string,
 * it must be parse to int.
 * @param {!Array.<!ydn.db.schema.Store>=} opt_stores store schemas.
 * @constructor
 * @struct
 */
ydn.db.schema.Database = function(opt_version, opt_stores) {

  /**
   * @type {number|undefined}
   */
  var ver;
  /**
   * @type {DatabaseSchema}
   */
  var json;
  var stores = opt_stores;
  if (goog.isObject(opt_version)) {
    json = opt_version;
    if (goog.DEBUG) {
      var fields = ['version', 'stores', 'fullTextCatalogs'];
      for (var key in json) {
        if (json.hasOwnProperty(key) && goog.array.indexOf(fields, key) == -1) {
          throw new ydn.debug.error.ArgumentException('Unknown field: ' + key +
              ' in schema.');
        }
      }
    }
    ver = json['version'];
    stores = [];
    var stores_json = json.stores || [];
    if (goog.DEBUG && !goog.isArray(stores_json)) {
      throw new ydn.debug.error.ArgumentException('stores must be array');
    }
    /**
     * Default ext store name.
     * @type {string|undefined}
     * @private
     */
    this.default_text_store_name_ = json['defaultTextStoreName'];
    for (var i = 0; i < stores_json.length; i++) {
      var store = ydn.db.schema.Store.fromJSON(stores_json[i]);
      if (goog.DEBUG) {
        var idx = goog.array.findIndex(stores, function(x) {
          return x.name == store.getName();
        });
        if (idx != -1) {
          throw new ydn.debug.error.ArgumentException('duplicate store name "' +
              store.getName() + '".');
        }

      }
      stores.push(store);
    }
  } else if (goog.isString(opt_version)) {
    ver = opt_version.length == 0 ?
        undefined : parseFloat(opt_version);
  } else if (goog.isNumber(opt_version)) {
    ver = opt_version;
  }

  if (goog.isDef(ver)) {
    if (!goog.isNumber(ver) || ver < 0) {
      throw new ydn.debug.error.ArgumentException('Invalid version: ' +
          ver + ' (' + opt_version + ')');
    }
    if (isNaN(ver)) {
      ver = undefined;
    }
  }
  if (goog.isDef(opt_stores) && (!goog.isArray(opt_stores) ||
      opt_stores.length > 0 && !(opt_stores[0] instanceof ydn.db.schema.Store)))
  {
    throw new ydn.debug.error.ArgumentException('stores');
  }

  /**
   * @type {number|undefined}
   */
  this.version = ver;

  this.is_auto_version_ = !goog.isDef(this.version);

  /**
   * @final
   * @type {!Array.<!ydn.db.schema.Store>}
   */
  this.stores = stores || [];
  var full_text_indexes = [];
  if (json && json.fullTextCatalogs) {
    goog.asserts.assertArray(json.fullTextCatalogs, 'fullTextCatalogs');
    for (var i = 0; i < json.fullTextCatalogs.length; i++) {
      var full_text_index = ydn.db.schema.fulltext.Catalog.fromJson(
          json.fullTextCatalogs[i]);
      full_text_indexes[i] = full_text_index;
      if (!this.getStore(full_text_index.getName())) {
        var p_indexes = [
          new ydn.db.schema.Index('k', ydn.db.schema.DataType.TEXT),
          new ydn.db.schema.Index('v', ydn.db.schema.DataType.TEXT)
        ];
        var full_text_store_schema = new ydn.db.schema.Store(
            full_text_index.getName(), 'id', false, undefined, p_indexes);
        this.stores.push(full_text_store_schema);
      }
    }
  }
  /**
   * @final
   * @type {Array.<ydn.db.schema.fulltext.Catalog>}
   * @private
   */
  this.full_text_schema_ = full_text_indexes;
};


/**
 * @return {number} number of full text indexes.
 */
ydn.db.schema.Database.prototype.countFullTextIndex = function() {
  return this.full_text_schema_.length;
};


/**
 * @param {number} idx
 * @return {ydn.db.schema.fulltext.Catalog}
 */
ydn.db.schema.Database.prototype.fullTextIndex = function(idx) {
  return this.full_text_schema_[idx];
};


/**
 * @param {string} name
 * @return {ydn.db.schema.fulltext.Catalog}
 */
ydn.db.schema.Database.prototype.getFullTextIndex = function(name) {
  return goog.array.find(this.full_text_schema_, function(x) {
    return x.getName() == name;
  });
};


/**
 * Get default text store.
 * @return string}
 */
ydn.db.schema.Database.prototype.getDefaultTextStoreName = function() {
  goog.asserts.assertString(this.default_text_store_name_,
      'defaultTextStoreName is not defined in the database schema');
  return this.default_text_store_name_;
};


/**
 * @override
 * @return {!DatabaseSchema} database schema in json.
 */
ydn.db.schema.Database.prototype.toJSON = function() {

  var stores = goog.array.map(this.stores, function(x) {return x.toJSON()});

  var sch = /** @type {DatabaseSchema} */ ({});
  sch.stores = stores;
  if (goog.isDef(this.version)) {
    sch.version = this.version;
  }
  return sch;
};


/**
 *
 * @type {boolean} auto version status.
 * @private
 */
ydn.db.schema.Database.prototype.is_auto_version_ = false;


/**
 * Current database version.
 * @type {number|undefined}
 */
ydn.db.schema.Database.prototype.version;


/**
 * Get schema version.
 * @return {number|undefined} version.
 */
ydn.db.schema.Database.prototype.getVersion = function() {
  return this.version;
};


/**
 * Update database schema for auto schema mode.
 * @param {number} version must be number type.
 */
ydn.db.schema.Database.prototype.setVersion = function(version) {
  goog.asserts.assert(this.is_auto_version_,
      'autoversion schema cannot set a version');
  goog.asserts.assertNumber(version, 'version must be a number');
  this.version = version;
};


/**
 *
 * @return {boolean} true if auto version.
 */
ydn.db.schema.Database.prototype.isAutoVersion = function() {
  return this.is_auto_version_;
};


/**
 *
 * @return {boolean} true if auto schema.
 */
ydn.db.schema.Database.prototype.isAutoSchema = function() {
  return false;
};


/**
 *
 * @return {!Array.<string>} list of store names.
 */
ydn.db.schema.Database.prototype.getStoreNames = function() {
  return goog.array.map(this.stores, function(x) {return x.getName();});
};


/**
 *
 * @param {number} idx index of stores.
 * @return {ydn.db.schema.Store} store schema at the index.
 */
ydn.db.schema.Database.prototype.store = function(idx) {
  return this.stores[idx] || null;
};


/**
 *
 * @return {number} number of store.
 */
ydn.db.schema.Database.prototype.count = function() {
  return this.stores.length;
};


/**
 *
 * @param {string} name store name.
 * @return {ydn.db.schema.Store} store if found.
 */
ydn.db.schema.Database.prototype.getStore = function(name) {
  return /** @type {ydn.db.schema.Store} */ (goog.array.find(this.stores,
      function(x) {
        return x.getName() == name;
      }));
};


/**
 * Get index of store.
 * @param {string} name store name.
 * @return {number} index of store -1 if not found.
 */
ydn.db.schema.Database.prototype.getIndexOf = function(name) {
  return goog.array.indexOf(this.stores,
      function(x) {
        return x.name == name;
      });
};


/**
 *
 * @param {string} name store name.
 * @return {boolean} return true if name found in stores.
 */
ydn.db.schema.Database.prototype.hasStore = function(name) {

  return goog.array.some(this.stores, function(x) {
    return x.getName() == name;
  });
};


/**
 * Return an explination what is different between the schemas.
 * @param {ydn.db.schema.Database} schema schema from sniffing.
 * @param {boolean} hint_websql hint the give schema, so that property
 * that could not be reflect from the connection are filled.
 * @param {boolean} hint_idb hint the give schema, so that property
 * that could not be reflect from the connection are filled.
 * @return {string} return empty string if the two are similar.
 */
ydn.db.schema.Database.prototype.difference = function(schema, hint_websql,
                                                       hint_idb) {
  if (!schema || this.stores.length != schema.stores.length) {
    return 'Number of store: ' + this.stores.length + ' vs ' +
        schema.stores.length;
  }
  for (var i = 0; i < this.stores.length; i++) {
    var store = schema.getStore(this.stores[i].getName());
    // hint to sniffed schema, so that some lost info are recovered.
    if (store) {
      if (hint_websql) {
        store = store.hintForWebSql(this.stores[i]);
      }
      if (hint_idb) {
        store.hintForIdb(this.stores[i]);
      }
      var msg = this.stores[i].difference(store);
      if (msg.length > 0) {
        return 'store: "' + this.stores[i].getName() + '" ' + msg;
      }
    } else {
      return 'missing object store "' + this.stores[i].getName() + '"';
    }
  }

  return '';
};


/**
 *
 * @param {ydn.db.schema.Database} schema schema.
 * @return {boolean} true if given schema is similar to this schema.
 */
ydn.db.schema.Database.prototype.similar = function(schema) {
  return this.difference(schema, false, false).length == 0;
};


/**
 * @param name
 * @return {ydn.db.schema.fulltext.Catalog}
 */
ydn.db.schema.Database.prototype.getFullTextSchema = function(name) {
  return goog.array.find(this.full_text_schema_, function(x) {
    return x.getName() == name;
  });
};


/**
 *
 * @return {!Array.<string>} Return list of store names.
 */
ydn.db.schema.Database.prototype.listStores = function() {
  if (!this.store_names) {
    /**
     * @final
     * @type {!Array.<string>}
     */
    this.store_names = goog.array.map(this.stores, function(x) {
      return x.getName();
    });
  }
  return this.store_names;
};

// Input 65
/**
 * @fileoverview Mutable database schema.
 */


goog.provide('ydn.db.schema.EditableDatabase');
goog.require('ydn.db.schema.Database');



/**
 *
 * @param {DatabaseSchema|number|string=} opt_version version, if string,
 * it must be parse to int.
 * @param {!Array.<!ydn.db.schema.Store>=} opt_stores store schemas.
 * @constructor
 * @extends {ydn.db.schema.Database}
 */
ydn.db.schema.EditableDatabase = function(opt_version, opt_stores) {
  goog.base(this, opt_version, opt_stores);
};
goog.inherits(ydn.db.schema.EditableDatabase, ydn.db.schema.Database);


/**
 * @override
 */
ydn.db.schema.EditableDatabase.prototype.isAutoSchema = function() {
  return true;
};


/**
 *
 * @param {!ydn.db.schema.Store} table store.
 */
ydn.db.schema.EditableDatabase.prototype.addStore = function(table) {
  this.stores.push(table);
};

// Input 66
/**
 * @fileoverview About this file.
 *
 * User: kyawtun
 * Date: 7/9/12
 */

goog.provide('ydn.error');
goog.provide('ydn.error.ArgumentException');
goog.provide('ydn.error.NotImplementedException');
goog.provide('ydn.error.ConstraintError');
goog.provide('ydn.error.NotSupportedException');
goog.provide('ydn.error.InternalError');
goog.provide('ydn.error.InvalidOperationException');
goog.provide('ydn.error.InvalidOperationError');




/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.ArgumentException = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.ArgumentException);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.ArgumentException';
};
goog.inherits(ydn.error.ArgumentException, Error);

/// FIXME: how come compiler strip out prototype name ?

//
///** @override */
//ydn.error.ArgumentException.prototype.name = 'ydn.ArgumentException';



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.NotSupportedException = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.NotSupportedException);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.NotSupportedException';
};
goog.inherits(ydn.error.ArgumentException, Error);

/** @override */
ydn.error.NotSupportedException.prototype.name = 'ydn.error.NotSupportedException';


/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.NotImplementedException = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.NotImplementedException);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.NotImplementedException';
};
goog.inherits(ydn.error.NotImplementedException, Error);

///** @override */
//ydn.error.NotImplementedException.prototype.name = 'ydn.NotImplementedException';



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.InternalError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.InternalError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.InternalError';
};
goog.inherits(ydn.error.InternalError, Error);

ydn.error.InternalError.prototype.name = 'ydn.InternalError';


/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.ConstraintError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.ConstraintError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.ConstraintError';
};
goog.inherits(ydn.error.ConstraintError, Error);

ydn.error.ConstraintError.prototype.name = 'ydn.error.ConstraintError';



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.InvalidOperationException = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.InvalidOperationException);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.InvalidOperationException';
};
goog.inherits(ydn.error.ArgumentException, Error);

///** @override */
//ydn.error.InvalidOperationException.prototype.name = 'ydn.InvalidOperationException';



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
ydn.error.InvalidOperationError = function(opt_msg) {

  // Ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, ydn.error.InvalidOperationError);
  } else {
    this.stack = new Error().stack || '';
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }
  this.name = 'ydn.error.InvalidOperationError';
};
goog.inherits(ydn.error.InvalidOperationError, Error);

//
///** @override */
//ydn.error.InvalidOperationError.prototype.name = 'ydn.InvalidOperationError';
// Input 67
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Some utilities on top of goog.object module.
 *
 */

goog.provide('ydn.object');
goog.require('ydn.json');


/**
 * Deep object cloning by JSON stringify/parse
 *
 * @param {Object|Array} obj object to be cloned.
 * @return {Object|Array} cloned object.
 */
ydn.object.clone = function(obj) {
  return ydn.json.parse(ydn.json.stringify(obj));
};


/**
 * Deeply compare two objects or arrays.
 *
 * @param {*} obj1 object 1 to be compare with.
 * @param {*} obj2 object 2 to be compare with.
 * @param {Object=} opt_ignore_fields optional field to be ignore in comparing.
 * @return {boolean} true if same contents.
 */
ydn.object.equals = function(obj1, obj2, opt_ignore_fields) {
  opt_ignore_fields = opt_ignore_fields || {};
  if (!goog.isDefAndNotNull(obj1) || !goog.isDefAndNotNull(obj2)) {
    return false;
  } else if (goog.isArrayLike(obj1) && goog.isArrayLike(obj2)) {
    var arr1 = obj1;
    var arr2 = /** @type {Array} */ (obj2);
    if (obj1.length != obj2.length) {
      return false;
    }
    for (var i = 0; i < obj1.length; i++) {
      var idx = goog.array.find(arr2, function(ele) {
        return ydn.object.equals(ele, arr1[i]);
      });
      if (idx == -1) {
        // console.log('obj2 do not have ' + obj1[i]);
        return false;
      }
    }
    return true;
  } else if (goog.isArrayLike(obj1)) {
    return obj1.length == 1 && ydn.object.equals(obj1[0], obj2);
  } else if (goog.isArrayLike(obj2)) {
    return obj2.length == 1 && ydn.object.equals(obj2[0], obj1);
  } else if (goog.isObject(obj1) && goog.isObject(obj1)) {
    for (var key in obj1) {
      if (obj1.hasOwnProperty(key) && !opt_ignore_fields[key]) {
        var same = ydn.object.equals(obj1[key], obj2[key]);
        if (!same) {
          return false;
        }
      }
    }
    for (var key in obj2) {
      if (obj2.hasOwnProperty(key) && !opt_ignore_fields[key]) {
        var same = ydn.object.equals(obj1[key], obj2[key]);
        if (!same) {
          return false;
        }
      }
    }
    return true;
  } else {
    return obj1 === obj2;
  }
};


/**
 * Length of array-like object. If it define {@code length} number field, it
 * return that value.
 *
 * @param {Object} obj object of concern.
 * @return {number} return number of hasOwnProperty fields.
 */
ydn.object.length = function(obj) {
  if (obj && obj['length'] && goog.isNumber(obj['length'])) {
    return obj.length;
  }
  var count = 0;
  for (var id in obj) {
    if (obj.hasOwnProperty(id)) {
      count++;
    }
  }
  return count;
};


/**
 * @see {@link goog.object.extend}
 * @param {Object} target  The source object.
 * @param {...Object} var_args The objects from which values will be copied.
 * @return {Object} extended object.
 */
ydn.object.extend = function(target, var_args) {
  var out = ydn.object.clone(target);
  for (var key in var_args) {
    if (var_args.hasOwnProperty(key)) {
      out[key] = var_args[key];
    }
  }
  return out;
};


/**
 * Construct array of having value v.
 * @param {*} v element value.
 * @param {number} n number of items.
 * @return {!Array} array preallocated with value v.
 */
ydn.object.reparr = function(v, n) {
  // this is the most efficient way in chrome.
  // IE10 can be more efficient by preallocating.
  // new Array(n); // preallocating.
  var arr = [];
  for (var i = 0; i < n; i++) {
    arr[i] = v;
  }
  return arr;
};


/**
 * Take the first field of an object
 * @final
 * @param {*} row row.
 * @return {*} the first field of object in row value.
 */
ydn.object.takeFirst = function(row) {
  if (!row) {
    return;
  }
  for (var key in row) {
    if (row.hasOwnProperty(key)) {
      return row[key];
    }
  }
  return undefined;
};

// Input 68
/**
 * @license Copyright 2012 YDN Authors, Yathit. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");.
 */
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Storage provider.
 *
 * Create and maintain database connection and provide robust transaction
 * objects upon request. Storage mechanism providers implement
 * ydn.db.con.IDatabase interface.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.con.Storage');
goog.require('ydn.db');
goog.require('ydn.db.base');
goog.require('ydn.db.con.IDatabase');
goog.require('ydn.db.con.IStorage');
goog.require('ydn.db.events.StorageEvent');
goog.require('ydn.db.schema.EditableDatabase');
goog.require('ydn.debug.error.ArgumentException');
goog.require('ydn.error.ConstraintError');
goog.require('ydn.object');



/**
 * Create a storage and connect to suitable database connection.
 *
 * The storage is ready to use on created, but transaction requested
 * my buffered until connection is established.
 *
 * If database name is provided, this will immediately initialize
 * the connection. Database name can be set later by using {@link #setName}
 * method.
 *
 * This grantee that the connected database has the similar schema as specified
 * in the input. If dissimilar between the two schema, the version change
 * is issued and alter the schema to match the input schema.
 *
 * @see {@link goog.db} Google Closure Library DB module.
 * @param {string=} opt_dbname database name.
 * @param {ydn.db.schema.Database|DatabaseSchema=} opt_schema database schema
 * or its configuration in JSON format. If not provided, default empty
 * auto-schema is used.
 * @param {!StorageOptions=} opt_options options.
 * @throws {ConstraintError} if fix version is used, but client database
 * schema is dissimilar.
 * @implements {ydn.db.con.IStorage}
 * @constructor
 */
ydn.db.con.Storage = function(opt_dbname, opt_schema, opt_options) {

  var options = opt_options || {};

  if (goog.DEBUG) {
    var fields = ['autoSchema', 'connectionTimeout', 'size', 'mechanisms',
      'policy', 'isSerial', 'Encryption'];
    for (var key in options) {
      if (options.hasOwnProperty(key) &&
          goog.array.indexOf(fields, key) == -1) {
        throw new ydn.debug.error.ArgumentException('Unknown attribute "' +
            key + '" in options.');
      }
    }
    if (options.mechanisms) {
      if (!goog.isArray(options.mechanisms)) {
        throw new ydn.debug.error.ArgumentException('mechanisms attribute ' +
            'must be an array but ' + goog.typeOf(options.mechanisms) +
            ' found.');
      }
      for (var i = 0; i < options.mechanisms.length; i++) {
        if (!goog.array.contains(ydn.db.con.Storage.PREFERENCE,
            options.mechanisms[i])) {
          throw new ydn.debug.error.ArgumentException('Invalid mechanism "' +
              options.mechanisms[i] + '"');
        }
      }
    }
  }

  /**
   * @final
   * @type {!Array.<string>}
   */
  this.mechanisms = options.mechanisms || ydn.db.con.Storage.PREFERENCE;

  /**
   * @final
   */
  this.size = options.size;

  /**
   * @final
   */
  this.connectionTimeout = goog.isDef(options.connectionTimeout) ?
      options.connectionTimeout :
      ydn.db.con.Storage.DEBUG ?
      1000 : goog.DEBUG ? 30 * 1000 : 30 * 60 * 1000;

  this.db_ = null;

  /**
   * Transaction queue
   * @private
   * @final
   * @type {!Array.<{
   *    fnc: Function,
   *    scopes: Array.<string>,
   *    mode: ydn.db.base.TransactionMode,
   *    oncompleted: function(ydn.db.base.TxEventTypes, *)
   *  }>}
   */
  this.txQueue_ = [];

  this.in_version_change_tx_ = false;

  var schema;
  if (opt_schema instanceof ydn.db.schema.Database) {
    schema = opt_schema;
  } else if (goog.isObject(opt_schema)) {
    /**
     * @type {!DatabaseSchema}
     */
    var schema_json = opt_schema;
    if (options.autoSchema || !goog.isDef(schema_json.stores)) {
      schema = new ydn.db.schema.EditableDatabase(schema_json);
    } else {
      schema = new ydn.db.schema.Database(schema_json);
    }

    var n = schema_json.stores ? schema_json.stores.length : 0;
    for (var i = 0; i < n; i++) {
      var store = schema.getStore(schema_json.stores[i].name);
      if (schema_json.stores[i].Sync) {
        this.addSynchronizer(store, schema_json.stores[i].Sync);
      }
    }
  } else {
    schema = new ydn.db.schema.EditableDatabase();
  }

  var has_valid_encryption = this.setEncryption(options.Encryption);
  /**
   * @final
   * @protected
   * @type {!ydn.db.schema.Database}
   */
  this.schema = schema;
  for (var i = 0; i < this.schema.count(); i++) {
    if (this.schema.store(i).isEncrypted()) {
      if (goog.DEBUG && !has_valid_encryption) {
        throw new Error('encryption option must be defined');
      }
      this.addEncryption(this.schema.store(i));
    }
  }

  if (goog.isDef(opt_dbname)) {
    this.setName(opt_dbname);
  }

  /**
   * Event dipatcher, initialized in ydn.db.tr.events
   * @protected
   */
  this.event_target = null;

  /**
   * On ready handler.
   * @type {goog.async.Deferred}
   * @private
   */
  this.df_on_ready_ = new goog.async.Deferred();
};


/**
 * @protected
 * @define {boolean} turn on debug flag to dump object.
 */
ydn.db.con.Storage.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.con.Storage.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.con.Storage');


/**
 * Get current schema.
 * @param {function(ydn.db.schema.Database)=} opt_callback schema in database.
 * @return {DatabaseSchema} schema in memory. Null if not connected.
 */
ydn.db.con.Storage.prototype.getSchema = function(opt_callback) {
  if (goog.isDef(opt_callback)) {
    var callback = function(schema) {
      opt_callback(schema.toJSON());
    };
    if (this.db_) {
      this.db_.getSchema(callback);
    } else {
      var me = this;
      goog.asserts.assertFunction(callback, 'schema'); // compiler complained.
      var get_schema = function(tx) {
        me.db_.getSchema(callback, tx);
      };
      this.transaction(get_schema, null, ydn.db.base.TransactionMode.READ_ONLY);
    }
  }
  return this.schema ? /** @type {!DatabaseSchema} */ (this.schema.toJSON()) :
      null;
};


/**
 * Add a store schema to current database schema on auto schema generation
 * mode {@see #auto_schema}.
 * If the store already exist it will be updated as necessary.
 * @param {!StoreSchema|!ydn.db.schema.Store} store_schema store schema.
 * @return {!goog.async.Deferred} promise.
 */
ydn.db.con.Storage.prototype.addStoreSchema = function(store_schema) {

  /**
   *
   * @type {ydn.db.schema.Store}
   */
  var new_store = store_schema instanceof ydn.db.schema.Store ?
      store_schema : ydn.db.schema.Store.fromJSON(store_schema);

  var store_name = store_schema.name;
  var store = this.schema.getStore(store_name);
  if (!new_store.similar(store)) {

    var action = store ? 'update' : 'add';

    if (this.schema instanceof ydn.db.schema.EditableDatabase) {
      // do update
      var schema = /** @type {ydn.db.schema.EditableDatabase} */ (this.schema);
      schema.addStore(new_store);
      if (this.db_) {
        this.db_.close();
        this.db_ = null;
        return this.connectDatabase();
      } else {
        return goog.async.Deferred.succeed(false);
      }
    } else {
      var msg = goog.DEBUG ? 'Cannot ' + action + ' store: ' +
          store_name + '. Not auto schema generation mode.' : '';
      throw new ydn.error.ConstraintError(msg);
    }
  } else {
    return goog.async.Deferred.succeed(false); // no change required
  }
};


/**
 * Set database name. This will initialize the database.
 * @throws {Error} name already defined.
 * @param {string} db_name set database name.
 */
ydn.db.con.Storage.prototype.setName = function(db_name) {
  if (this.db_) {
    throw new ydn.debug.error.InvalidOperationException('Already' +
        ' connected with ' + this.db_name);
  }
  goog.asserts.assertString(db_name, 'database name must be a string' +
      ' but found ' + db_name + ' of type ' + (typeof db_name));

  /**
   * @final
   */
  this.db_name = db_name;
  this.connectDatabase();

};


/**
 * @type {string}
 * @protected
 */
ydn.db.con.Storage.prototype.db_name;


/**
 * @type {ydn.db.con.IDatabase}
 * @private
 */
ydn.db.con.Storage.prototype.db_;


/**
 * @type {number|undefined}
 * @protected
 */
ydn.db.con.Storage.prototype.size;


/**
 * @type {number}
 * @protected
 */
ydn.db.con.Storage.prototype.connectionTimeout;


/**
 * Super class must not mutate schema data.
 * @type {!ydn.db.schema.Database} database schema as requested.
 */
ydn.db.con.Storage.prototype.schema;


/**
 *
 * @return {string} name of database.
 */
ydn.db.con.Storage.prototype.getName = function() {
  return this.db_name;
};


/**
 * Specified storage mechanism ordering.
 * The default represent
 * IndexedDB, WebSql, localStorage and in-memory store.
 * @const
 * @type {!Array.<string>}
 */
ydn.db.con.Storage.PREFERENCE = [
  ydn.db.base.Mechanisms.IDB,
  ydn.db.base.Mechanisms.WEBSQL,
  ydn.db.base.Mechanisms.LOCAL_STORAGE,
  ydn.db.base.Mechanisms.SESSION_STORAGE,
  ydn.db.base.Mechanisms.USER_DATA,
  ydn.db.base.Mechanisms.MEMORY_STORAGE];


/**
 * Create database instance.
 * @protected
 * @param {string} db_type database type.
 * @return {ydn.db.con.IDatabase} newly created database instance.
 */
ydn.db.con.Storage.prototype.createDbInstance = function(db_type) {
  // super class will inject db instance.
  return null;
};


/**
 * Initialize suitable database if {@code dbname} and {@code schema} are set,
 * starting in the following order of preference.
 * @protected
 * @return {!goog.async.Deferred} promise.
 */
ydn.db.con.Storage.prototype.connectDatabase = function() {
  // handle version change

  var me = this;

  var df = new goog.async.Deferred();
  var resolve = function(is_connected, ev) {
    if (is_connected) {
      me.logger.finest(me + ': ready.');
      me.last_queue_checkin_ = NaN;

      /**
       * Error event received from the database. Bubble up to the application
       * for logging purpose.
       * @param {Error} e event.
       */
      db.onError = function(e) {
        var event = new ydn.db.events.StorageErrorEvent(me, e);
        me.dispatchDbEvent(event);
      };

      /**
       * @param {Error} e event.
       */
      db.onFail = function(e) {
        var event = new ydn.db.events.StorageFailEvent(me, e);
        me.dispatchDbEvent(event);
        me.db_ = null; // database can no longer be used on fail.
      };


      /**
       * @param {Event} e event.
       */
      db.onVersionChange = function(e) {
        me.dispatchDbEvent(e);
      };

      setTimeout(function() {
        // dispatch asynchroniously so that any err on running db request
        // are not caught under deferred object.

        me.dispatchReady(ev);
        me.popTxQueue_();
      }, 10);

      df.callback(ev);
    } else {
      me.logger.warning(me + ': database connection fail ' + ev.name);
      setTimeout(function() {
        var event = new ydn.db.events.StorageFailEvent(me, ev);
        me.dispatchReady(event);
        me.purgeTxQueue_(ev);
      }, 10);
      df.errback(ev);
    }
  };

  /**
   * The connected database instance.
   * @type {ydn.db.con.IDatabase}
   */
  var db = null;

  // go according to ordering
  var preference = this.mechanisms;
  for (var i = 0; i < preference.length; i++) {
    var db_type = preference[i].toLowerCase();
    db = this.createDbInstance(db_type);
    if (db) { // run-time detection
      db = this.createDbInstance(db_type);
      break;
    }
  }

  if (goog.isNull(db)) {

    var e = new ydn.error.ConstraintError('No storage mechanism found.');

    var event = new ydn.db.events.StorageFailEvent(this, e);
    resolve(false, event);
  } else {

    this.init(); // let super class to initialize.

    db.connect(this.db_name, this.schema).addCallbacks(function(old_version) {
      this.db_ = db;
      var event = new ydn.db.events.StorageEvent(ydn.db.events.Types.READY,
          this, parseFloat(db.getVersion()), parseFloat(old_version), null);
      resolve(true, event);
    }, function(e) {
      this.logger.warning(this + ': opening fail');
      resolve(false, e);
    }, this);
  }

  return df;

};


/**
 *
 * @return {string|undefined} database mechanism type.
 */
ydn.db.con.Storage.prototype.getType = function() {
  if (this.db_) {
    return this.db_.getType();
  } else {
    return undefined;
  }
};


/**
 * Add handler on database ready event.
 * @param {function(this: T, Error?)} cb in case of database fail to open, invoke with
 * the error, otherwise null.
 * @param {T=} opt_scope
 * @template T
 */
ydn.db.con.Storage.prototype.onReady = function(cb, opt_scope) {
  this.df_on_ready_.addBoth(cb, opt_scope);
};


/**
 * Handle ready event by dispatching 'ready' event.
 * @param {ydn.db.events.Event} ev event.
 */
ydn.db.con.Storage.prototype.dispatchReady = function(ev) {
  var me = this;
  // using setTimeout here prevent transaction overlap error.
  setTimeout(function() {
    if (ev instanceof ydn.db.events.StorageErrorEvent) {
      var err = /** @type {ydn.db.events.StorageErrorEvent} */ (ev);
      me.df_on_ready_.errback(err.error);
    } else {
      me.df_on_ready_.callback();
    }
    me.dispatchDbEvent(ev);
  }, 4);

};


/**
 *
 * @return {boolean} true on ready.
 */
ydn.db.con.Storage.prototype.isReady = function() {
  return !!this.db_ && this.db_.isReady();
};


/**
 * Database database is instantiated, but may not ready.
 * Subclass may perform initialization.
 * When ready, deferred call are invoked and transaction queue
 * will run.
 * @protected
 */
ydn.db.con.Storage.prototype.init = function() {
};


/**
 * Close the database.
 */
ydn.db.con.Storage.prototype.close = function() {
  if (this.db_) {
    this.db_.close();
    this.db_ = null;
    this.logger.finest(this + ' closed');
  }
};


/**
 * Get nati database instance.
 * @return {*} database instance.
 * @deprecated no longer exported.
 */
ydn.db.con.Storage.prototype.getDbInstance = function() {
  return this.db_ ? this.db_.getDbInstance() : null;
};


/**
 * @type {number}
 * @private
 */
ydn.db.con.Storage.prototype.last_queue_checkin_ = NaN;


/**
* @const
* @type {number}
*/
ydn.db.con.Storage.QUEUE_LIMIT = 100;


/**
 * Return number elements in tx queue.
 * @return {number}
 */
ydn.db.con.Storage.prototype.countTxQueue = function() {
  return this.txQueue_.length;
};


/**
 * Run the first transaction task in the queue. DB must be ready to do the
 * transaction.
 * @private
 */
ydn.db.con.Storage.prototype.popTxQueue_ = function() {

  var task = this.txQueue_.shift();
  if (task) {
    this.logger.finest('pop tx queue[' + (this.txQueue_.length + 1) + ']');
    this.transaction(task.fnc, task.scopes, task.mode, task.oncompleted);
  }
  this.last_queue_checkin_ = goog.now();
};


/**
 * Push a transaction job to the queue.
 * @param {Function} trFn function that invoke in the transaction.
 * @param {Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_on_completed handler.
 * @private
 */
ydn.db.con.Storage.prototype.pushTxQueue_ = function(trFn, store_names,
    opt_mode, opt_on_completed) {
  this.logger.finest('push tx queue[' + this.txQueue_.length + ']');
  this.txQueue_.push({
    fnc: trFn,
    scopes: store_names,
    mode: opt_mode,
    oncompleted: opt_on_completed
  });

  if (goog.DEBUG && this.txQueue_.length > ydn.db.con.Storage.QUEUE_LIMIT &&
      (this.txQueue_.length % ydn.db.con.Storage.QUEUE_LIMIT) == 0) {
    this.logger.warning('Transaction queue stack size is ' +
        this.txQueue_.length +
        '. It is too large, possibility due to incorrect usage.');
  }
};


/**
 * Abort the queuing tasks.
 * @private
 * @param {Error} e error.
 */
ydn.db.con.Storage.prototype.purgeTxQueue_ = function(e) {
  if (this.txQueue_) {
    this.logger.info('Purging ' + this.txQueue_.length +
        ' transactions request.');
    var task;
    while (task = this.txQueue_.shift()) {
      // task.fnc(null); this will cause error
      if (task.oncompleted) {
        task.oncompleted(ydn.db.base.TxEventTypes.ERROR, e);
      }
    }
  }
};


/**
 * Flag to indicate on version change transaction.
 * @type {boolean}
 * @private
 */
ydn.db.con.Storage.prototype.in_version_change_tx_ = false;


/**
 * Run a transaction.
 *
 * @param {Function} trFn function that invoke in the transaction.
 * @param {Array.<string>} store_names list of keys or
 * store name involved in the transaction.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode, default to 'readonly'.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_on_completed handler.
 * @final
 */
ydn.db.con.Storage.prototype.transaction = function(trFn, store_names,
    opt_mode, opt_on_completed) {

  var names = store_names;

  if (goog.isString(store_names)) {
    names = [store_names];
  } else if (!goog.isDefAndNotNull(store_names)) {
    names = null;
  } else {
    if (goog.DEBUG) {
      if (!goog.isArrayLike(store_names)) {  // could be  DOMStringList or Array
        throw new ydn.debug.error.ArgumentException(
            'store names must be an array');
      } else if (store_names.length == 0) {
        throw new ydn.debug.error.ArgumentException(
            'number of store names must more than 0');
      } else {
        for (var i = 0; i < store_names.length; i++) {
          if (!goog.isString(store_names[i])) {
            throw new ydn.debug.error.ArgumentException('store name at ' + i +
                ' must be string but found ' + typeof store_names[i]);
          }
        }
      }
    }
  }

  var is_ready = !!this.db_ && this.db_.isReady();
  if (!is_ready || this.in_version_change_tx_) {
    // a "versionchange" transaction is still running, a InvalidStateError
    // exception will be thrown
    this.pushTxQueue_(trFn, names, opt_mode, opt_on_completed);
    return;
  }

  var me = this;

  var mode = goog.isDef(opt_mode) ? opt_mode :
      ydn.db.base.TransactionMode.READ_ONLY;

  if (mode == ydn.db.base.TransactionMode.VERSION_CHANGE) {
    this.in_version_change_tx_ = true;
  }

  var on_complete = function(type, ev) {
    if (goog.isFunction(opt_on_completed)) {
      opt_on_completed(type, ev);
      opt_on_completed = undefined;
    }
    if (mode == ydn.db.base.TransactionMode.VERSION_CHANGE) {
      me.in_version_change_tx_ = false;
    }
    me.popTxQueue_();
  };

  //console.log('core running ' + trFn.name);
  this.db_.doTransaction(function(tx) {
    trFn(tx);
    trFn = null;
  }, names, mode, on_complete);

};


/**
 *
 * @return {boolean} true if auto version mode.
 */
ydn.db.con.Storage.prototype.isAutoVersion = function() {
  return this.schema.isAutoVersion();
};


/**
 *
 * @return {boolean} true if auto schema mode.
 */
ydn.db.con.Storage.prototype.isAutoSchema = function() {
  return this.schema.isAutoSchema();
};


/**
 * ydn.db.sync module will override this method to inject sync functions.
 * @param {ydn.db.schema.Store} store store object.
 * @param {StoreSyncOptionJson} option synchronization options.
 * @protected
 */
ydn.db.con.Storage.prototype.addSynchronizer = function(store, option) {
  this.logger.warning('Synchronization option for ' + store.getName() +
      ' ignored.');
};


/**
 * ydn.db.sync module will override this method to inject sync functions.
 * @param {ydn.db.schema.Store} store store object.
 * @protected
 */
ydn.db.con.Storage.prototype.addEncryption = function(store) {
  this.logger.warning('Encryption option for ' + store.getName() +
      ' ignored.');
};


/**
 * @param {Object} encryption secret name.
 * @return {boolean}
 */
ydn.db.con.Storage.prototype.setEncryption = function(encryption) {
  return false;
};


/**
 * ydn.db.sync module will override this method to inject sync functions.
 * @param {!ydn.db.schema.Store} store store object.
 * @param {!ydn.db.schema.fulltext.Catalog} option synchronization options.
 * @protected
 */
ydn.db.con.Storage.prototype.addFullTextIndexer = function(store, option) {
  this.logger.warning('Full text indexer option for ' + store.getName() +
      ' ignored.');
};


/**
 * For validating user input.
 * @return {!Array.<string>} list of event types.
 */
ydn.db.con.Storage.prototype.getEventTypes = function() {
  return ['created', 'error', 'fail', 'ready', 'deleted', 'updated',
    'versionchange'];
};


/**
 * Dispatch event if installed.
 * @param {goog.events.EventLike} event optional args.
 */
ydn.db.con.Storage.prototype.dispatchDbEvent = function(event) {

};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.con.Storage.prototype.toString = function() {
    return 'Storage:' + this.db_;
  };
}






// Input 69
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Parallel transaction executor.
 */

goog.provide('ydn.db.tr.ParallelTxExecutor');
goog.require('ydn.debug.error.InternalError');



/**
 *
 * @param {ydn.db.base.Transaction} tx transaction.
 * @param {number} tx_no tx no.
 * @param {Array.<string>} store_names store lists for explicit tx scope.
 * @param {ydn.db.base.TransactionMode?} mode mode for explicit tx scope.
 * @constructor
 * @struct
 */
ydn.db.tr.ParallelTxExecutor = function(tx, tx_no, store_names, mode) {
  this.tx_ = tx;
  this.tx_no_ = tx_no;
  this.scopes_ = goog.array.clone(store_names);
  this.mode_ = mode;
  this.oncompleted_handlers = [];
};


/**
 * @type {ydn.db.base.Transaction}
 * @private
 */
ydn.db.tr.ParallelTxExecutor.prototype.tx_ = null;


/**
 * @type {number}
 * @private
 */
ydn.db.tr.ParallelTxExecutor.prototype.tx_no_;


/**
 * @private
 * @type {Array.<Function>}
 */
ydn.db.tr.ParallelTxExecutor.prototype.oncompleted_handlers;


/**
 * @type {Array.<string>} list of sorted store names as transaction scope
 * @private
 */
ydn.db.tr.ParallelTxExecutor.prototype.scopes_;


/**
 * @type {ydn.db.base.TransactionMode?}
 * @private
 */
ydn.db.tr.ParallelTxExecutor.prototype.mode_;


/**
 *
 * @return {boolean} return true if thread has active transaction.
 */
ydn.db.tr.ParallelTxExecutor.prototype.isActive = function() {
  return !!this.tx_;
};


/**
 *
 * @return {ydn.db.base.Transaction} active transaction object.
 * @protected
 */
ydn.db.tr.ParallelTxExecutor.prototype.getTx = function() {
  return this.tx_;
};


/**
 *
 * @return {number} transaction count.
 */
ydn.db.tr.ParallelTxExecutor.prototype.getTxNo = function() {
  return this.tx_no_;
};


/**
 * Handler on tx completed.
 * @param {ydn.db.base.TxEventTypes} t tx event type.
 * @param {*} e error if it has.
 */
ydn.db.tr.ParallelTxExecutor.prototype.onCompleted = function(t, e) {
  goog.asserts.assert(this.isActive(), this.tx_no_ + ' already completed?');
  for (var i = 0; i < this.oncompleted_handlers.length; i++) {
    this.oncompleted_handlers[i](t, e);
  }
  this.oncompleted_handlers.length = 0;
  this.tx_ = null;
  this.scopes_ = null;
  this.oncompleted_handlers = null;
};


/**
 *
 * @param {Function} on_tx tx function.
 * @param {function(ydn.db.base.TxEventTypes, *)=} opt_on_completed handler.
 */
ydn.db.tr.ParallelTxExecutor.prototype.executeTx = function(on_tx,
                                                            opt_on_completed) {
  if (this.tx_) {
    if (opt_on_completed) {
      this.oncompleted_handlers.push(opt_on_completed);
    }
    on_tx(this.tx_);
  } else {
    throw new ydn.debug.error.InternalError(
        'tx committed on ParallelTxExecutor');
  }
};


/**
 *
 * @param {!Array.<string>} scopes store names as tx scope.
 * @param {ydn.db.base.TransactionMode} mode tx mode.
 * @return {boolean} true if in same scope.
 */
ydn.db.tr.ParallelTxExecutor.prototype.sameScope = function(scopes, mode) {
  if (!this.scopes_ || !this.mode_) {
    return false;
  }
  if (mode != this.mode_) {
    return false;
  }
  if (this.scopes_.length != scopes.length) {
    return false;
  }
  for (var i = 0; i < scopes.length; i++) {
    if (this.scopes_.indexOf(scopes[i]) == -1) {
      return false;
    }
  }
  return true;
};


/**
 *
 * @param {!Array.<string>} store_names store names as tx scope.
 * @param {ydn.db.base.TransactionMode} mode mode tx mode.
 * @return {boolean} true if in sub scope.
 */
ydn.db.tr.ParallelTxExecutor.prototype.subScope = function(store_names, mode) {
  if (!this.scopes_ || !this.mode_) {
    return false;
  }
  if (mode != this.mode_) {
    if (this.mode_ != ydn.db.base.TransactionMode.READ_WRITE ||
        mode != ydn.db.base.TransactionMode.READ_ONLY) {
      return false;
    }
  }
  if (store_names.length > this.scopes_.length) {
    return false;
  }
  for (var i = 0; i < store_names.length; i++) {
    if (this.scopes_.indexOf(store_names[i]) == -1) {
      return false;
    }
  }
  return true;
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.tr.ParallelTxExecutor.prototype.toString = function() {
    return 'ParallelTxExecutor: txNo:' + this.tx_no_ + ' mode:' +
        this.mode_ + ' scopes:' + ydn.json.stringify(this.scopes_);
  };
}

// Input 70
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Parallel transaction thread.
 *
 * Transaction is created as necessary resulting parallel transactions.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.tr.Parallel');
goog.require('ydn.db.tr.Thread');
goog.require('ydn.db.tr.ParallelTxExecutor');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Create transaction queue providing methods to run in non-overlapping
 * transactions.
 *
 * @extends {ydn.db.tr.Thread}
 * @param {!ydn.db.tr.Storage} storage base storage.
 * @param {number} ptx_no transaction queue number.
 * @param {ydn.db.tr.Thread.Policy=} opt_policy
 * @param {!Array.<string>=} opt_store_names store names as scope.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode as scope.
 * @param {number=} opt_max_tx_no limit number of transaction created.
 * @constructor
 * @struct
 */
ydn.db.tr.Parallel = function(storage, ptx_no, opt_policy,
                              opt_store_names, opt_mode, opt_max_tx_no) {

  goog.base(this, storage, ptx_no, opt_policy,
      opt_store_names, opt_mode, opt_max_tx_no);

  /**
   *
   * @type {ydn.db.tr.ParallelTxExecutor}
   * @private
   */
  this.pl_tx_ex_ = null;

  /**
   * Transaction object is sed when receiving a request before result df
   * callback and set null after that callback so that it can be aborted
   * in the callback.
   * In general, this tx may be different from running tx.
   * @type {ydn.db.base.Transaction}
   * @protected
   */
  this.p_request_tx = null;


};
goog.inherits(ydn.db.tr.Parallel, ydn.db.tr.Thread);


/**
 * @const
 * @type {boolean}
 */
ydn.db.tr.Parallel.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.Parallel.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.Parallel');


/**
 *
 * @return {ydn.db.tr.ParallelTxExecutor}
 */
ydn.db.tr.Parallel.prototype.getPlTx = function() {
  return this.pl_tx_ex_;
};


/**
 *
 * @return {boolean} return true if thread has active transaction.
 */
ydn.db.tr.Parallel.prototype.isActive = function() {
  return !!this.pl_tx_ex_ && this.pl_tx_ex_.isActive();
};


/**
 *
 * @param {!Array.<string>} store_names
 * @param {ydn.db.base.TransactionMode} mode
 * @return {boolean}
 * @protected
 */
ydn.db.tr.Parallel.prototype.sameScope = function(store_names, mode) {
  return this.pl_tx_ex_.sameScope(store_names, mode);
};


/**
 *
 * @param {!Array.<string>} store_names
 * @param {ydn.db.base.TransactionMode} mode
 * @return {boolean}
 * @protected
 */
ydn.db.tr.Parallel.prototype.subScope = function(store_names, mode) {
  return this.pl_tx_ex_.subScope(store_names, mode);
};


/**
 * Abort an active transaction.
 * @throws InvalidStateError if transaction is not active.
 */
ydn.db.tr.Parallel.prototype.abort = function() {
  this.logger.finer(this + ': aborting');
  ydn.db.tr.Thread.abort(this.p_request_tx);
};


/**
 * Return cache executor object or create on request. This have to be crated
 * Lazily because, we can initialize it only when transaction object is active.
 * @protected
 * @return {ydn.db.crud.req.IRequestExecutor} get executor.
 */
ydn.db.tr.Parallel.prototype.getExecutor = goog.abstractMethod;


/**
 * @param {!Array.<string>} store_names store names for scope.
 * @param {ydn.db.base.TransactionMode} mode tx mode.
 * @return {boolean} return true if given scope and mode is compatible with
 * active transaction and should be reuse.
 * @protected
 */
ydn.db.tr.Parallel.prototype.reusedTx = function(store_names, mode) {
  if (this.policy == ydn.db.tr.Thread.Policy.MULTI) {
    return this.pl_tx_ex_.subScope(store_names, mode);
  } else if (this.policy == ydn.db.tr.Thread.Policy.REPEAT) {
    return this.pl_tx_ex_.sameScope(store_names, mode);
  } else if (this.policy == ydn.db.tr.Thread.Policy.ALL) {
    return true;
  } else {
    return false; // SINGLE and ATOMIC
  }
};


/**
 * @inheritDoc
 */
ydn.db.tr.Parallel.prototype.processTx = function(callback, store_names,
    opt_mode, on_completed) {

  var label;

  if (this.scope_store_names) {
    store_names = this.scope_store_names;
  }
  if (this.scope_mode) {
    opt_mode = this.scope_mode;
  }

  var mode = goog.isDef(opt_mode) ?
      opt_mode : ydn.db.base.TransactionMode.READ_ONLY;

  var me = this;
  var pl_tx_ex;

  var completed_handler = function(type, event) {
    me.logger.fine(label + ' ' + type);
    if (pl_tx_ex) {
      // if transaction_process was not called due to database fail
      pl_tx_ex.onCompleted(type, event);
    }
    me.r_no_ = 0;
  };

  var transaction_process = function(tx) {
    me.tx_no_++;
    pl_tx_ex = new ydn.db.tr.ParallelTxExecutor(
        tx, me.tx_no_, store_names, mode);
    label = me.getLabel();
    me.logger.fine(label + ' BEGIN ' +
        ydn.json.stringify(store_names) + ' ' + mode);
    me.pl_tx_ex_ = pl_tx_ex;
    me.pl_tx_ex_.executeTx(callback, on_completed);
  };

  var reused = this.isActive() && this.reusedTx(store_names, mode);
  if (ydn.db.tr.Parallel.DEBUG) {
    var act = this.isActive() ? 'active' : 'inactive';
    window.console.log(this +
        ' ' + this.pl_tx_ex_ +
        (reused ? ' reusing ' + act + ' transaction' :
            ' opening ' + act + ' transaction ') +
        ' for mode:' + mode + ' scopes:' +
        ydn.json.stringify(store_names));
  }

  if (reused) {
    this.pl_tx_ex_.executeTx(callback, on_completed);
  } else {
    if (this.max_tx_no && this.tx_no_ >= this.max_tx_no) {
      throw new ydn.debug.error.InvalidOperationException(
          'Exceed maximum number of transactions of ' + this.max_tx_no);
    }
    this.getStorage().transaction(transaction_process, store_names, mode,
        completed_handler);
  }

};


/**
 * @inheritDoc
 */
ydn.db.tr.Parallel.prototype.request = function(method, store_names, opt_mode,
                                                opt_on_complete) {
  var req = goog.base(this, 'request', method, store_names, opt_mode,
      opt_on_complete);
  var mode = opt_mode || ydn.db.base.TransactionMode.READ_ONLY;
  var me = this;

  if (ydn.db.tr.Parallel.DEBUG) {
    var rdn = 'SN' + Math.random();
    rdn = rdn.replace('.', '');
    window.console.log(this + ' scheduling to execute ' + store_names + ' ' +
        mode + ' ' + rdn);
  }

  this.processTx(function(tx) {
    if (ydn.db.tr.Parallel.DEBUG) {
      window.console.log(me + ' executing ' + rdn);
    }
    me.r_no_++;
    var rq_label = me.getLabel() + 'R' + me.r_no_;
    req.setTx(tx, rq_label);
  }, store_names, mode, opt_on_complete);
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.tr.Parallel.prototype.exec = function(df, callback, store_names, mode,
                                             on_completed) {

  var me = this;
  var rq_label;

  if (ydn.db.tr.Parallel.DEBUG) {
    var rdn = 'SN' + Math.random();
    rdn = rdn.replace('.', '');
    window.console.log(this + ' scheduling to execute ' + store_names + ' ' +
        mode + ' ' + rdn);
  }

  this.processTx(function(tx) {
    if (ydn.db.tr.Parallel.DEBUG) {
      window.console.log(this + ' executing ' + rdn);
    }
    me.r_no_++;
    rq_label = me.getLabel() + 'R' + me.r_no_;
    /**
     *
     * @param {*} result
     * @param {boolean=} is_error
     */
    var resultCallback = function(result, is_error) {
      me.p_request_tx = tx; // so that we can abort it.
      rq_label = me.getLabel() + 'R' + me.r_no_;
      if (is_error) {
        me.logger.finer(rq_label + ' ERROR');
        df.errback(result);
      } else {
        me.logger.finer(rq_label + ' SUCCESS');
        df.callback(result);
      }
      me.p_request_tx = null;
    };
    me.logger.finer(rq_label + ' BEGIN');
    callback(tx, rq_label, resultCallback);
    callback = null;
    me.logger.finer(rq_label + ' END');
  }, store_names, mode, on_completed);
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.Parallel.prototype.toString = function() {
    var s = this.p_request_tx ? '*' : '';
    return 'Parallel:' + this.policy + ':' + this.getLabel() + s;
  };
}


// Input 71
/**
 * @fileoverview Transaction queue.
 *
 * A transaction is used to crate non-overlapping transaction so that each
 * database methods are atomic and run in order.
 */


goog.provide('ydn.db.tr.AtomicParallel');
goog.require('ydn.db.tr.Thread');
goog.require('ydn.db.tr.Parallel');
goog.require('ydn.debug.error.NotSupportedException');



/**
 * Create transaction queue providing methods to run in non-overlapping
 * transactions.
 *
 * @param {!ydn.db.tr.Storage} storage base storage.
 * @param {number} ptx_no transaction queue number.
 * @constructor
 * @extends {ydn.db.tr.Parallel}
 */
ydn.db.tr.AtomicParallel = function(storage, ptx_no) {

  goog.base(this, storage, ptx_no, ydn.db.tr.Thread.Policy.SINGLE);

};
goog.inherits(ydn.db.tr.AtomicParallel, ydn.db.tr.Parallel);


/**
 * @const
 * @type {boolean}
 */
ydn.db.tr.AtomicParallel.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.tr.AtomicParallel.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.tr.AtomicParallel');


/**
 * @inheritDoc
 */
ydn.db.tr.AtomicParallel.prototype.reusedTx = function(scopes, mode) {
  return false;
};


/**
 * @inheritDoc
 */
ydn.db.tr.AtomicParallel.prototype.request = function(method, scope, opt_mode) {
  var req_setDbValue, result, is_error;
  var me = this;
  /**
   * @param {ydn.db.base.TxEventTypes} t event type.
   * @param {*} e error.
   */
  var onComplete = function(t, e) {
    // console.log('onComplete', t, result);
    req.removeTx();
    me.logger.finer('transaction ' + t);
    if (req_setDbValue) {
      if (t != ydn.db.base.TxEventTypes.COMPLETE) {
        is_error = true;
        result = e;
      }
      req_setDbValue(result, is_error);
    } else {
      var err = new ydn.db.TimeoutError();
      req.setDbValue(err, true);
    }
  };
  var req = goog.base(this, 'request', method, scope, opt_mode, onComplete);
  // intersect request result to make atomic
  req.await(function(value, has_error, rtn) {
    // console.log('req success', value);
    is_error = has_error;
    result = value;
    req_setDbValue = rtn;
  });
  return req;
};


/**
 * @inheritDoc
 */
ydn.db.tr.AtomicParallel.prototype.exec = function(df, callback, store_names,
    mode, on_completed) {
  // intersect request result to make atomic
  var result;
  var is_error;
  var cdf = new goog.async.Deferred();
  cdf.addCallbacks(function(x) {
    is_error = false;
    result = x;
  }, function(e) {
    is_error = true;
    result = e;
  });
  var completed_handler = function(t, e) {
    // console.log('completed_handler ' + t + ' ' + e);
    if (t != ydn.db.base.TxEventTypes.COMPLETE) {
      df.errback(e);
    } else if (is_error === true) {
      df.errback(result);
    } else if (is_error === false) {
      df.callback(result);
    } else {
      var err = new ydn.db.TimeoutError();
      df.errback(err);
    }
    if (on_completed) {
      on_completed(t, e);
      on_completed = undefined;
    }
  };
  goog.base(this, 'exec', cdf, callback, store_names, mode,
      completed_handler);
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.tr.AtomicParallel.prototype.toString = function() {
    return 'Atomic' + goog.base(this, 'toString');
  };
}

// Input 72
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Base database service provider.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.tr.Storage');
goog.require('ydn.db.con.Storage');
goog.require('ydn.db.tr.AtomicParallel');
goog.require('ydn.db.tr.AtomicSerial');
goog.require('ydn.db.tr.DbOperator');
goog.require('ydn.db.tr.Parallel');
goog.require('ydn.db.tr.Serial');



/**
 * Create storage providing method to run in transaction.
 *
 * @param {string=} opt_dbname database name.
 * @param {!ydn.db.schema.Database|DatabaseSchema=} opt_schema database schema
 * or its configuration in JSON format. If not provided, default empty schema
 * is used.
 * schema used in chronical order.
 * @param {!StorageOptions=} opt_options options.
 * @extends {ydn.db.con.Storage}
 * @constructor
 */
ydn.db.tr.Storage = function(opt_dbname, opt_schema, opt_options) {
  goog.base(this, opt_dbname, opt_schema, opt_options);

  this.ptx_no = 0;

  var is_serial = true;
  var req_type = ydn.db.tr.Thread.Policy.SINGLE;
  if (opt_options) {
    if (goog.isDef(opt_options.isSerial)) {
      is_serial = !!opt_options.isSerial;
    }
    if (opt_options.policy) {
      req_type = /** @type {ydn.db.tr.Thread.Policy} */
          (opt_options.policy);
    }
  }

  var tx_thread = this.newTxQueue(req_type, is_serial);

  /**
   * here we must define sync thread first, so that it is ready when
   * executing main thread.
   * This sync thread is used internally.
   * @final
   */
  this.sync_thread = this.newTxQueue(ydn.db.tr.Thread.Policy.ATOMIC, false);

  /**
   * main thread.
   * @final
   */
  this.db_operator = this.newOperator(tx_thread, this.sync_thread);


};
goog.inherits(ydn.db.tr.Storage, ydn.db.con.Storage);


/**
 * @type {ydn.db.tr.Thread}
 * @protected
 */
ydn.db.tr.Storage.prototype.sync_thread;


/**
 * @type {ydn.db.tr.DbOperator}
 * @protected
 */
ydn.db.tr.Storage.prototype.db_operator;


/**
 *
 * @type {number}
 * @protected
 */
ydn.db.tr.Storage.prototype.ptx_no = 0;


/**
 * Create a new db operator.
 * @param {ydn.db.tr.Thread.Policy} request_type thread policy.
 * @param {boolean=} opt_is_serial serial request.
 * @param {!Array.<string>=} opt_store_names store names for tx scope.
 * @param {ydn.db.base.StandardTransactionMode=} opt_mode tx mode.
 * @param {number=} opt_max_tx limit number of transaction.
 * @return {ydn.db.tr.DbOperator} db operator.
 * @final
 */
ydn.db.tr.Storage.prototype.branch = function(request_type, opt_is_serial,
    opt_store_names, opt_mode, opt_max_tx) {

  var mode;
  if (opt_mode == ydn.db.base.StandardTransactionMode.READ_ONLY) {
    mode = ydn.db.base.TransactionMode.READ_ONLY;
  } else if (opt_mode == ydn.db.base.StandardTransactionMode.READ_WRITE) {
    mode = ydn.db.base.TransactionMode.READ_WRITE;
  }

  var tx_thread = this.newTxQueue(request_type, opt_is_serial,
      opt_store_names, mode, opt_max_tx);
  return this.newOperator(tx_thread, this.sync_thread);
};


/**
 * @param {!ydn.db.tr.Thread} tx_thread transaction thread.
 * @param {ydn.db.tr.Thread} sync_thread thread for synchronization.
 * @return {!ydn.db.tr.DbOperator} the db operator.
 * @protected
 */
ydn.db.tr.Storage.prototype.newOperator = function(tx_thread, sync_thread) {
  return new ydn.db.tr.DbOperator(this, this.schema,
      tx_thread, sync_thread);
};


/**
 * Create a new thread queue.
 * @param {ydn.db.tr.Thread.Policy} request_type thread policy.
 * @param {boolean=} opt_is_serial serial request.
 * @param {!Array.<string>=} opt_store_names store names as scope.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode as scope.
 * @param {number=} opt_max_tx limit number of transaction.
 * @return {!ydn.db.tr.Thread} new transactional storage.
*/
ydn.db.tr.Storage.prototype.newTxQueue = function(request_type, opt_is_serial,
    opt_store_names, opt_mode, opt_max_tx) {

  if (opt_is_serial) {
    if (request_type == ydn.db.tr.Thread.Policy.MULTI ||
        request_type == ydn.db.tr.Thread.Policy.REPEAT ||
        request_type == ydn.db.tr.Thread.Policy.ALL ||
        request_type == ydn.db.tr.Thread.Policy.SINGLE) {
      return new ydn.db.tr.Serial(this, this.ptx_no++, request_type,
          opt_store_names, opt_mode, opt_max_tx);
    } else if (request_type == ydn.db.tr.Thread.Policy.ATOMIC) {
      return new ydn.db.tr.AtomicSerial(this, this.ptx_no++);
    } else {
      throw new ydn.debug.error.ArgumentException('Invalid requestType "' +
          request_type + '"');
    }
  } else {
    if (request_type == ydn.db.tr.Thread.Policy.MULTI ||
        request_type == ydn.db.tr.Thread.Policy.REPEAT ||
        request_type == ydn.db.tr.Thread.Policy.ALL ||
        request_type == ydn.db.tr.Thread.Policy.SINGLE) {
      return new ydn.db.tr.Parallel(this, this.ptx_no++, request_type,
          opt_store_names, opt_mode, opt_max_tx);
    } else if (request_type == ydn.db.tr.Thread.Policy.ATOMIC) {
      return new ydn.db.tr.AtomicParallel(this, this.ptx_no++);
    } else {
      throw new ydn.debug.error.ArgumentException('Invalid requestType "' +
          request_type + '"');
    }
  }
};


/**
 * Run a new transaction.
 * @param {function(!ydn.db.tr.DbOperator)} trFn function that invoke in the
 * transaction.
 * @param {Array.<string>=} opt_store_names list of store name involved in the
 * transaction. Default to all store names.
 * @param {ydn.db.base.StandardTransactionMode=} opt_mode mode, default to
 * 'readonly'.
 * @return {!ydn.db.Request}
 * @final
 */
ydn.db.tr.Storage.prototype.run = function(trFn, opt_store_names, opt_mode) {

  if (goog.DEBUG && arguments.length > 3) {
    throw new ydn.debug.error.ArgumentException('too many input arguments, ' +
        'run accept not more than 3 input arguments, but ' +
        arguments.length + ' found.');
  }
  this.ptx_no++;

  var store_names = opt_store_names || this.schema.getStoreNames();

  // NOTE: ydn.db.base.TransactionMode can be number (as in old definition).
  var mode = ydn.db.base.TransactionMode.READ_ONLY;
  if (opt_mode) {
    if (opt_mode == ydn.db.base.StandardTransactionMode.READ_WRITE) {
      mode = ydn.db.base.TransactionMode.READ_WRITE;
    } else if (opt_mode != ydn.db.base.StandardTransactionMode.READ_ONLY) {
      throw new ydn.debug.error.ArgumentException('Invalid transaction mode "' +
          opt_mode + '"');
    }
  }

  var tx_thread = this.newTxQueue(ydn.db.tr.Thread.Policy.ALL, false,
      store_names, mode, 1);
  var db_operator = this.newOperator(tx_thread, this.sync_thread);
  var req = new ydn.db.Request(ydn.db.Request.Method.RUN);
  /**
   * @param {ydn.db.base.TxEventTypes} type
   * @param {*} e
   */
  var onComplete = function(type, e) {
    req.removeTx();
    var success = type === ydn.db.base.TxEventTypes.COMPLETE;
    req.setDbValue(tx_thread.getTxNo(), !success);
  };

  var me = this;
  tx_thread.processTx(function(tx) {
    me.logger.finest('executing run in transaction on ' + tx_thread);
    req.setTx(tx, tx_thread.getLabel() + 'R0'); // Request 0
    trFn(db_operator);
  }, store_names, mode, onComplete);

  return req;
};


/**
 * Spawn a new transaction thread using generator coroutine.
 * @param {function(!ydn.db.tr.DbOperator)} trFn function that invoke in the
 * transaction.
 * @param {Array.<string>=} opt_store_names list of store name involved in the
 * transaction. Default to all store names.
 * @param {ydn.db.base.StandardTransactionMode=} opt_mode mode, default to
 * 'readonly'.
 * @param {number=} opt_max_tx limit number of transactions created.
 * @return {!ydn.db.Request}
 * @final
 */
ydn.db.tr.Storage.prototype.spawn = function(trFn, opt_store_names, opt_mode,
                                             opt_max_tx) {

  if (goog.DEBUG && arguments.length > 3) {
    throw new ydn.debug.error.ArgumentException('too many input arguments, ' +
        'run accept not more than 3 input arguments, but ' +
        arguments.length + ' found.');
  }
  this.ptx_no++;

  var store_names = opt_store_names || this.schema.getStoreNames();

  // NOTE: ydn.db.base.TransactionMode can be number (as in old definition).
  var mode = ydn.db.base.TransactionMode.READ_ONLY;
  if (opt_mode) {
    if (opt_mode == ydn.db.base.StandardTransactionMode.READ_WRITE) {
      mode = ydn.db.base.TransactionMode.READ_WRITE;
    } else if (opt_mode != ydn.db.base.StandardTransactionMode.READ_ONLY) {
      throw new ydn.debug.error.ArgumentException('Invalid transaction mode "' +
          opt_mode + '"');
    }
  }
  // use parallel queue, because generator yielding will make tx creation
  // linear.
  var tx_thread = this.newTxQueue(ydn.db.tr.Thread.Policy.ALL, false,
      store_names, mode, opt_max_tx);
  var db_operator = this.newOperator(tx_thread, this.sync_thread);

  var req = new ydn.db.Request(ydn.db.Request.Method.RUN);
  var onTxCompleted;
  /**
   * @param {ydn.db.base.TxEventTypes} type
   * @param {*} e
   */
  var onComplete = function(type, e) {
    req.removeTx();
    var done = type == ydn.db.base.TxEventTypes.COMPLETE ||
        type == ydn.db.base.TxEventTypes.ABORT;
    if (done) {
      onTxCompleted(type, e);
    }
  };

  var me = this;
  tx_thread.processTx(function(tx) {
    me.logger.finest('executing run in transaction on ' + tx_thread);
    req.setTx(tx, tx_thread.getLabel() + 'R0'); // Request 0
    var gen = trFn(db_operator);
    onTxCompleted = tx_thread.setGenerator(gen, req);
    gen.next();
  }, store_names, mode, onComplete);

  return req;
};


/**
 * Current transaction count. Useful for debugging or performance analysis
 * purpose only.
 * @return {number} transaction number.
 */
ydn.db.tr.Storage.prototype.getTxNo = function() {
  return this.db_operator ? this.db_operator.getTxNo() : NaN;
};

// Input 73
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Provide atomic CRUD database operations.
 *
 * The actual operation is implemented in transaction queue. This class create
 * a new transaction queue as necessary.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.crud.Storage');
goog.require('ydn.db.crud.DbOperator');
goog.require('ydn.db.crud.IOperator');
goog.require('ydn.db.crud.req.IRequestExecutor');
goog.require('ydn.db.events.RecordEvent');
goog.require('ydn.db.events.StoreEvent');
goog.require('ydn.db.tr.Storage');
goog.require('ydn.object');



/**
 * Construct storage providing atomic CRUD database operations on implemented
 * storage mechanisms.
 *
 * This class do not execute database operation, but create a non-overlapping
 * transaction queue on ydn.db.crud.DbOperator and all operations are
 * passed to it.
 *
 *
 * @param {string=} opt_dbname database name.
 * @param {(ydn.db.schema.Database|!DatabaseSchema)=} opt_schema database
 * schema
 * or its configuration in JSON format. If not provided, default empty schema
 * is used.
 * @param {!StorageOptions=} opt_options options.
 * @extends {ydn.db.tr.Storage}
 * @implements {ydn.db.crud.IOperator}
 * @constructor
 */
ydn.db.crud.Storage = function(opt_dbname, opt_schema, opt_options) {

  goog.base(this, opt_dbname, opt_schema, opt_options);

  var schema = this.schema;

  for (var i = 0; i < schema.countFullTextIndex(); i++) {
    var ft_schema = schema.fullTextIndex(i);
    var store = schema.getStore(ft_schema.getName());
    if (store) {
      if (!store.hasIndex('k')) {
        throw new ydn.debug.error.ArgumentException('full text index store "' +
            store.getName() + '" must have "keyword" index');
      }
      if (!store.hasIndex('v')) {
        throw new ydn.debug.error.ArgumentException('full text index store "' +
            store.getName() + '" must have "keyword" index');
      }
      if (store.getKeyPath() != 'id') {
        throw new ydn.debug.error.ArgumentException('full text index store "' +
            store.getName() + '" must use "id" as key path.');
      }
    } else {
      throw new ydn.debug.error.ArgumentException('full text index store "' +
          ft_schema.getName() + '" required.');
    }
    for (var j = 0; j < ft_schema.count(); j++) {
      var index = ft_schema.index(j);
      var source_store = schema.getStore(index.getStoreName());
      if (source_store) {
        this.addFullTextIndexer(source_store, ft_schema);
      } else {
        throw new ydn.debug.error.ArgumentException('full text source store "' +
            index.getStoreName() + '" does not exist for full text index "' +
            ft_schema.getName() + '"');
      }
    }
  }

};
goog.inherits(ydn.db.crud.Storage, ydn.db.tr.Storage);


/**
 * @override
 */
ydn.db.crud.Storage.prototype.init = function() {

  goog.base(this, 'init');

};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.newOperator = function(tx_thread, sync_thread) {
  return new ydn.db.crud.DbOperator(this, this.schema, tx_thread, sync_thread);
};


/**
 * Cast operator.
 * @return {ydn.db.crud.DbOperator}
 */
ydn.db.crud.Storage.prototype.getCoreOperator = function() {
  return /** @type {ydn.db.crud.DbOperator} */ (this.db_operator);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.add = function(store, value, opt_key) {
  return this.getCoreOperator().add(store, value, opt_key);
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.count = function(store_name, key_range, index,
                                               unique) {
  return this.getCoreOperator().count(store_name, key_range, index, unique);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.get = function(arg1, arg2) {
  return this.getCoreOperator().get(arg1, arg2);
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.keys = function(store_name, arg2, arg3, arg4,
                                              arg5, arg6, arg7) {
  //  return ydn.db.crud.DbOperator.prototype.keys.apply(
  //    /** @type {ydn.db.crud.DbOperator} */ (this.base_tx_queue),
  //    Array.prototype.slice.call(arguments));

  // above trick is the same effect as follow
  //return this.getCoreOperator().keys(store_name, arg2, arg3,
  //  arg4, arg5, arg6, arg7);
  // but it preserve argument length

  return this.getCoreOperator().keys(store_name, arg2, arg3, arg4, arg5, arg6,
      arg7);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.values = function(arg1, arg2, arg3, arg4, arg5,
                                                arg6) {
  return this.getCoreOperator().values(arg1, arg2, arg3, arg4, arg5, arg6);
};


/**
 * List
 * @param {ydn.db.base.QueryMethod} type
 * @param {string} store_name
 * @param {string=} index_name
 * @param {ydn.db.KeyRange|ydn.db.IDBKeyRange=} key_range
 * @param {number=} limit
 * @param {number=} offset
 * @param {boolean=} reverse
 * @param {boolean=} unique
 * @param {Array.<IDBKey|undefined>=} opt_pos last cursor position.
 * @return {!ydn.db.Request}
 */
ydn.db.crud.Storage.prototype.list = function(type, store_name, index_name,
    key_range, limit, offset, reverse, unique, opt_pos) {
  return this.getCoreOperator().list(type, store_name, index_name,
      key_range, limit, offset, reverse, unique, opt_pos);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.load = function(store_name_or_schema, data,
                                              delimiter)  {
  return this.getCoreOperator().load(store_name_or_schema, data, delimiter);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.put = function(store, value, opt_key) {
  return this.getCoreOperator().put(store, value, opt_key);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.clear = function(arg1, arg2, arg3) {
  return this.getCoreOperator().clear(arg1, arg2, arg3);
};


/**
 * @inheritDoc
 */
ydn.db.crud.Storage.prototype.remove = function(arg1, arg2, arg3) {
  return this.getCoreOperator().remove(arg1, arg2, arg3);
};


if (goog.DEBUG) {
  /** @override */
  ydn.db.crud.Storage.prototype.toString = function() {
    var s = 'Storage:' + this.getName();
    if (this.isReady()) {
      s += ' [' + this.getType() + ']';
    }
    return s;
  };
}



// Input 74
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Provide iteration query.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.core.Storage');
goog.require('ydn.db.core.DbOperator');
goog.require('ydn.db.crud.Storage');



/**
 * Construct storage providing atomic CRUD database operations on implemented
 * storage mechanisms.
 *
 * This class do not execute database operation, but create a non-overlapping
 * transaction queue on ydn.db.crud.DbOperator and all operations are
 * passed to it.
 *
 *
 * @param {string=} opt_dbname database name.
 * @param {(ydn.db.schema.Database|!DatabaseSchema)=} opt_schema database
 * schema
 * or its configuration in JSON format. If not provided, default empty schema
 * is used.
 * @param {!StorageOptions=} opt_options options.
 * @extends {ydn.db.crud.Storage}
 * @implements {ydn.db.core.IOperator}
 * @constructor
 */
ydn.db.core.Storage = function(opt_dbname, opt_schema, opt_options) {

  goog.base(this, opt_dbname, opt_schema, opt_options);

};
goog.inherits(ydn.db.core.Storage, ydn.db.crud.Storage);


/**
 * Create a new operator.
 * @inheritDoc
 */
ydn.db.core.Storage.prototype.newOperator = function(tx_thread, sync_thread) {
  return new ydn.db.core.DbOperator(this, this.schema, tx_thread, sync_thread);
};


/**
 * Get casted operator.
 * @return {ydn.db.core.DbOperator} casted operator.
 */
ydn.db.core.Storage.prototype.getIndexOperator = function() {
  return /** @type {ydn.db.core.DbOperator} */ (this.db_operator);
};


/**
 *
 * @param {function(this: T, !ydn.db.core.req.ICursor)} callback icursor
 * handler.
 * @param {!ydn.db.Iterator} iter the cursor.
 * @param {ydn.db.base.TransactionMode=} opt_mode mode.
 * @param {T=} opt_scope optional callback scope.
 * @return {!ydn.db.Request} promise on completed.
 * @template T
 */
ydn.db.core.Storage.prototype.open = function(callback, iter, opt_mode,
                                              opt_scope) {
  return this.getIndexOperator().open(callback, iter, opt_mode, opt_scope);
};


/**
 * List record in a store.
 * @param {ydn.db.base.QueryMethod} mth keys method.
 * @param {!ydn.db.Iterator} iter iterator.
 * @param {number=} opt_limit limit.
 * @param {number=} opt_offset limit.
 * @return {!ydn.db.Request} request.
 */
ydn.db.core.Storage.prototype.listIter = function(mth, iter,
                                                  opt_limit, opt_offset) {
  return this.getIndexOperator().listIter(mth, iter,
      opt_limit, opt_offset);
};


/**
 * Cursor scan iteration.
 * @param {!ydn.db.algo.AbstractSolver|function(!Array, !Array): !Array} solver
 * solver.
 * @param {!Array.<!ydn.db.Iterator>} iterators the cursor.
 * @return {!goog.async.Deferred} promise on completed.
 */
ydn.db.core.Storage.prototype.scan = function(solver, iterators) {
  return this.getIndexOperator().scan(solver, iterators);
};


/**
 * @inheritDoc
 */
ydn.db.core.Storage.prototype.map = function(iterator, callback) {
  return this.getIndexOperator().map(iterator, callback);
};


/**
 * @inheritDoc
 */
ydn.db.core.Storage.prototype.reduce = function(iterator, callback,
                                                opt_initial) {
  return this.getIndexOperator().reduce(iterator, callback, opt_initial);
};

//goog.exportSymbol('ydn.db.Storage', ydn.db.core.Storage);

// Input 75
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query directly execute on raw cursor.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.query.Base');
goog.require('ydn.db.core.Storage');



/**
 * Query directly execute on raw cursor.
 * @param {ydn.db.core.DbOperator} db
 * @param {ydn.db.schema.Database} schema
 * @constructor
 * @struct
 */
ydn.db.query.Base = function(db, schema) {
  /**
   * @final
   * @protected
   * @type {ydn.db.core.DbOperator}
   */
  this.db = db;
  /**
   * @final
   * @protected
   * @type {ydn.db.schema.Database}
   */
  this.schema = schema;
};


/**
 * @define {boolean} debug flag.
 */
ydn.db.query.Base.DEBUG = false;


/**
 * Execute query and collect as an array. This method forces query execution.
 * @param {ydn.db.base.QueryMethod} mth query method.
 * @param {!ydn.db.Iterator} iterator
 * @param {number} limit
 * @param {number} offset
 * @return {!ydn.db.Request}
 */
ydn.db.query.Base.prototype.list = function(mth, iterator, limit, offset) {
  // console.log(this.iterator.getState(), this.iterator.getKey());
  var req = this.db.listIter(mth, iterator, limit, offset);
  return req;
};


/**
 * Patch object.
 * @param {!ydn.db.Iterator} iterator iterator.
 * @param {!Object|string|!Array.<string>} arg1 Patch object, field name or
 * field names.
 * @param {*=} opt_arg2 field value or field values.
 * @return {!ydn.db.Request}
 */
ydn.db.query.Base.prototype.patch = function(iterator, arg1, opt_arg2) {
  var req = this.db.open(function(cursor) {
    var val = /** @type {!Object} */ (cursor.getValue());
    if (goog.isString(arg1)) {
      ydn.db.utils.setValueByKeys(val, arg1, opt_arg2);
    } else if (goog.isArray(arg1)) {
      for (var i = 0; i < arg1.length; i++) {
        ydn.db.utils.setValueByKeys(val, arg1[i], opt_arg2[i]);
      }
    } else if (goog.isObject(arg1)) {
      for (var k in arg1) {
        if (arg1.hasOwnProperty(k)) {
          val[k] = arg1[k];
        }
      }
    }
    req.awaitDeferred(cursor.update(val));
  }, iterator, ydn.db.base.TransactionMode.READ_WRITE, this);
  return req;
};


/**
 * Execute query and collect as an array. This method forces query execution.
 * @param {!ydn.db.Iterator} iterator iterator.
 * @param {function(this: T, !ydn.db.core.req.ICursor)} cb
 * @param {T=} opt_scope
 * @return {!ydn.db.Request}
 * @template T
 */
ydn.db.query.Base.prototype.open = function(iterator, cb, opt_scope) {
  var req = this.db.open(cb, iterator,
      ydn.db.base.TransactionMode.READ_WRITE, opt_scope);
  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @param {ydn.db.Iterator} iter iterator.
 * @return {!ydn.db.Request}
 */
ydn.db.query.Base.prototype.count = function(iter) {
  var req;
  if (iter.isUnique()) {
    req = this.db.count(iter);
  } else if (iter.isIndexIterator()) {
    req = this.db.count(iter.getStoreName(), iter.getIndexName(),
        iter.getKeyRange());
  } else {
    req = this.db.count(iter.getStoreName(), iter.getKeyRange());
  }
  if (iter.getState() != ydn.db.Iterator.State.INITIAL) {
    // reset iteration state.
    req.addBoth(function() {
      if (iter.getState() != ydn.db.Iterator.State.WORKING) {
        iter.reset();
      }
    });
  }
  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @param {ydn.db.Iterator} iter iterator.
 * @return {!ydn.db.Request}
 */
ydn.db.query.Base.prototype.clear = function(iter) {
  var req = iter.isIndexIterator() ?
      this.db.clear(iter.getStoreName(), iter.getIndexName(), iter.keyRange()) :
      this.db.clear(iter.getStoreName(), iter.keyRange());
  return req;
};


// Input 76
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Sorted merge algorithm.
 *
 * Sorted merge algorithm join reference values of given iterators (and
 * streamers) to matching value by continuing them by highest reference value.
 *
 * http://dev.mysql.com/doc/refman/5.0/en/index-merge-optimization.html
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.algo.SortedMerge');
goog.require('ydn.db');
goog.require('ydn.db.algo.AbstractSolver');



/**
 *
 * @param {(!Array|!{push: Function}|!ydn.db.Streamer)=} out output receiver.
 * @param {number=} opt_limit limit.
 * @constructor
 * @extends {ydn.db.algo.AbstractSolver}
 */
ydn.db.algo.SortedMerge = function(out, opt_limit) {
  goog.base(this, out, opt_limit);

};
goog.inherits(ydn.db.algo.SortedMerge, ydn.db.algo.AbstractSolver);


/**
 * @define {boolean} debug flag.
 */
ydn.db.algo.SortedMerge.DEBUG = false;


/**
 * @inheritDoc
 */
ydn.db.algo.SortedMerge.prototype.solver = function(keys, values) {

  var advancement = [];

  var base_key = values[0];

  if (!goog.isDefAndNotNull(base_key)) {
    if (ydn.db.algo.SortedMerge.DEBUG) {
      window.console.log('SortedMerge: done.');
    }
    return [];
  }
  var all_match = true; // let assume
  var skip = false;     // primary_key must be skip
  var highest_key = base_key;
  var cmps = [];

  for (var i = 1; i < keys.length; i++) {
    if (goog.isDefAndNotNull(values[i])) {
      //console.log([values[0], values[i]])
      var cmp = ydn.db.cmp(base_key, values[i]);
      cmps[i] = cmp;
      if (this.is_reverse) {
        if (cmp == -1) {
          // base key is greater than ith key, so fast forward to ith key.
          all_match = false;
        } else if (cmp == 1) {
          // ith key is greater than base key. we are not going to get it
          all_match = false;
          skip = true; //
          if (ydn.db.cmp(values[i], highest_key) == -1) {
            highest_key = values[i];
          }
        }
      } else {
        if (cmp == 1) {
          // base key is greater than ith key, so fast forward to ith key.
          all_match = false;
        } else if (cmp == -1) {
          // ith key is greater than base key. we are not going to get it
          all_match = false;
          skip = true; //
          if (ydn.db.cmp(values[i], highest_key) == 1) {
            highest_key = values[i];
          }
        }
      }
      //i += this.degrees_[i]; // skip peer iterators.
    } else {
      all_match = false;
      skip = true;
    }
  }

  if (all_match) {
    // we get a match, so looking forward to next key.
    // all other keys are rewind
    for (var j = 0; j < keys.length; j++) {
      if (goog.isDefAndNotNull(values[j])) {
        advancement[j] = true;
      }
    }
  } else if (skip) {
    // all jump to highest key position.
    for (var j = 0; j < keys.length; j++) {
      if (goog.isDefAndNotNull(values[j])) {
        // we need to compare again, because intermediate highest
        // key might get cmp value of 0, but not the highest key
        if (this.is_reverse) {
          if (ydn.db.cmp(highest_key, values[j]) == -1) {
            advancement[j] = highest_key;
          }
        } else {
          if (ydn.db.cmp(highest_key, values[j]) == 1) {
            advancement[j] = highest_key;
          }
        }
      }
    }
  } else {
    // some need to catch up to base key
    var cmp_target = this.is_reverse ? -1 : 1;
    for (var j = 1; j < keys.length; j++) {
      if (cmps[j] === cmp_target) {
        advancement[j] = base_key;
      }
    }
  }

  if (ydn.db.algo.SortedMerge.DEBUG) {
    window.console.log('SortedMerge: match: ' + all_match +
        ', skip: ' + skip +
        ', highest_key: ' + JSON.stringify(highest_key) +
        ', keys: ' + JSON.stringify(keys) +
        ', cmps: ' + JSON.stringify(cmps) +
        ', advancement: ' + JSON.stringify(advancement));
  }

  if (all_match) {
    this.match_count++;
    //console.log(['match key', match_key, JSON.stringify(keys)]);
    if (this.out) {
      this.out.push(highest_key);
    }
    return advancement;
  } else {
    return {'continuePrimary': advancement};
  }
};

// Input 77
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query with multiple AND iterators.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.query.Restricted');
goog.require('ydn.db.core.Storage');
goog.require('ydn.db.algo.SortedMerge');



/**
 * Query directly execute on raw cursor.
 * @param {ydn.db.core.DbOperator} db
 * @param {ydn.db.schema.Database} schema
 * @constructor
 * @struct
 */
ydn.db.query.Restricted = function(db, schema) {
  /**
   * @final
   * @protected
   * @type {ydn.db.core.DbOperator}
   */
  this.db = db;
  /**
   * @final
   * @protected
   * @type {ydn.db.schema.Database}
   */
  this.schema = schema;
};


/**
 * @define {boolean} debug flag.
 */
ydn.db.query.Restricted.DEBUG = false;


/**
 * Execute query and collect as an array. This method forces query execution.
 * @param {ydn.db.base.QueryMethod} mth query method.
 * @param {[ydn.db.Iterator]} iterators
 * @param {number} limit
 * @return {!ydn.db.Request}
 */
ydn.db.query.Restricted.prototype.list = function(mth, iterators, limit) {
  // console.log(this.iterator.getState(), this.iterator.getKey());
  var out = [];
  var solver = new ydn.db.algo.SortedMerge(out, limit);
  var scan_req = this.db.scan(solver, iterators);
  var store_name = iterators[0].getStoreName();
  var req = scan_req.copy();
  scan_req.addCallbacks(function(p_keys) {
    if (mth == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
      req.callback(p_keys);
    } else {
      req.chainDeferred(this.db.values(store_name, p_keys));
    }
  }, function(e) {
    req.errback(e);
  }, this);

  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @param {ydn.db.Iterator} iter iterator.
 * @return {!ydn.db.Request}
 */
ydn.db.query.Restricted.prototype.count = function(iter) {
  var req;
  if (iter.isUnique()) {
    req = this.db.count(iter);
  } else if (iter.isIndexIterator()) {
    req = this.db.count(iter.getStoreName(), iter.getIndexName(),
        iter.getKeyRange());
  } else {
    req = this.db.count(iter.getStoreName(), iter.getKeyRange());
  }
  if (iter.getState() != ydn.db.Iterator.State.INITIAL) {
    // reset iteration state.
    req.addBoth(function() {
      if (iter.getState() != ydn.db.Iterator.State.WORKING) {
        iter.reset();
      }
    });
  }
  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @param {ydn.db.Iterator} iter iterator.
 * @return {!ydn.db.Request}
 */
ydn.db.query.Restricted.prototype.clear = function(iter) {
  var req = iter.isIndexIterator() ?
      this.db.clear(iter.getStoreName(), iter.getIndexName(), iter.keyRange()) :
      this.db.clear(iter.getStoreName(), iter.keyRange());
  return req;
};


// Input 78
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query builder class.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.Query');
goog.require('ydn.db.core.Storage');
goog.require('ydn.db.query.Base');
goog.require('ydn.db.query.Restricted');



/**
 * Query builder class.
 * @param {ydn.db.core.DbOperator} db
 * @param {ydn.db.schema.Database} schema
 * @param {!Array.<!ydn.db.Iterator>|!ydn.db.Iterator} iter key range.
 * @param {ydn.db.base.QueryMethod=} opt_type query type. Default to values.
 * @constructor
 * @struct
 */
ydn.db.Query = function(db, schema, iter, opt_type) {
  /**
   * @final
   * @protected
   * @type {ydn.db.core.DbOperator}
   */
  this.db = db;
  /**
   * @final
   * @protected
   * @type {ydn.db.schema.Database}
   */
  this.schema = schema;
  /**
   * @final
   * @protected
   * @type {ydn.db.base.QueryMethod}
   */
  this.type = opt_type || ydn.db.base.QueryMethod.NONE;
  /**
   * @final
   * @protected
   * @type {Array.<string>}
   */
  this.orders = [];
  /**
   * @final
   * @protected
   * @type {!Array.<!ydn.db.Iterator>}
   */
  this.iterators = goog.isArray(iter) ? iter : [iter];
  /**
   * @type {?function(*): boolean}
   * @private
   */
  this.filter_ = null;
  /**
   * @type {*}
   * @private
   */
  this.filter_scope_;
};


/**
 * @define {boolean} debug flag.
 */
ydn.db.Query.DEBUG = false;


/**
 * @return {!ydn.db.Query}
 */
ydn.db.Query.prototype.copy = function() {
  var iters = [];
  for (var i = 0; i < this.iterators.length; i++) {
    iters[i] = this.iterators[i].copy();
  }
  var q = new ydn.db.Query(this.db, this.schema, iters, this.type);
  q.filter_ = this.filter_;
  q.filter_scope_ = this.filter_scope_;
  return q;
};


/**
 * @return {!ydn.db.Query} return a new query.
 */
ydn.db.Query.prototype.reverse = function() {
  var iters = [];
  for (var i = 0; i < this.iterators.length; i++) {
    iters[i] = this.iterators[i].reverse();
  }
  return new ydn.db.Query(this.db, this.schema, iters,
      this.type);
};


/**
 * Set unique state of query.
 * @param {boolean} val
 * @return {!ydn.db.Query}
 */
ydn.db.Query.prototype.unique = function(val) {
  if (!goog.isBoolean(val)) {
    throw new ydn.debug.error.ArgumentException('unique value must be' +
        ' a boolean, but ' + typeof val + ' found');
  }
  if (this.iterators.length > 1) {
    throw new ydn.debug.error.ArgumentException('multi-iterators cannot be set' +
        ' unique property');
  }
  var iter = this.iterators[0];
  if (!iter.isIndexIterator()) {
    throw new ydn.debug.error.ArgumentException('primary key query is ' +
        'already unique');
  }
  return new ydn.db.Query(this.db, this.schema, [iter.unique(val)],
      this.type);
};


/**
 * Specify query order.
 * @param {string|Array.<string>} order
 * @return {!ydn.db.Query} return a new query.
 */
ydn.db.Query.prototype.order = function(order) {
  var orders = goog.isString(order) ? [order] : order;
  if (orders.length != 1) {
    throw new ydn.debug.error.ArgumentException('Multi ordering not' +
        ' implemented, wait for next release');
  }
  if (this.iterators.length > 1) {
    throw new ydn.debug.error.ArgumentException('order cannot be set');
  }
  var iter = this.iterators[0];
  var store = this.schema.getStore(iter.getStoreName());
  var kr = iter.getKeyRange();
  if (iter.isIndexIterator()) {
    if (iter.getIndexName() != orders[0]) {
      var index = [iter.getIndexName(), orders[0]];
      if (kr) {
        if (kr.lower == kr.upper) {
          var range = ydn.db.KeyRange.starts([kr.lower]);
          iter = new ydn.db.Iterator(iter.getStoreName(),
              index.join(', '), range, iter.isReversed(), iter.isUnique(),
              iter.isKeyIterator(), index);
        } else {
          throw new ydn.debug.error.ArgumentException('Not supported');
        }
      } else {
        iter = new ydn.db.Iterator(iter.getStoreName(),
            index.join(', '), null, iter.isReversed(), iter.isUnique(),
            iter.isKeyIterator(), index);
      }
    }
  } else {
    if (orders[0] != store.getKeyPath()) {
      if (kr) {
        throw new ydn.debug.error.ArgumentException('Not possible without' +
            ' using in memory sorting.');
      } else {
        iter = new ydn.db.IndexValueIterator(iter.getStoreName(),
            orders[0], null, iter.isReversed(), iter.isUnique());
      }
    }
  }
  return new ydn.db.Query(this.db, this.schema, iter, this.type);
};


/**
 * Create a new value cursor range iterator using where clause condition.
 * @param {string} index_name index name.
 * @param {string} op where operator.
 * @param {IDBKey} value rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.Query} return this for chaining.
 */
ydn.db.Query.prototype.where = function(index_name, op, value, opt_op2,
    opt_value2) {

  var iter = this.iterators[0];
  if (iter.hasKeyRange()) {
    throw new ydn.debug.error.ArgumentException(
        'joint query not implemented yet');
  } else {
    var store_name = iter.getStoreName();
    var store = this.schema.getStore(store_name);
    if (index_name == store.getKeyPath()) {
      iter = ydn.db.ValueIterator.where(store_name,
          op, value, opt_op2, opt_value2);
    } else {
      iter = ydn.db.IndexValueIterator.where(store_name,
          index_name, op, value, opt_op2, opt_value2);
    }
  }

  return new ydn.db.Query(this.db, this.schema, iter, this.type);
};


/**
 * @return {ydn.db.schema.Store}
 */
ydn.db.Query.prototype.getStore = function() {
  return this.schema.getStore(this.iterators[0].getStoreName());
};


/**
 * Select query result.
 * @param {string|!Array.<string>} field_name_s select field name(s).
 * @return {!ydn.db.Query}
 */
ydn.db.Query.prototype.select = function(field_name_s) {
  var store = this.getStore();
  var fields = goog.isArray(field_name_s) ? field_name_s : [field_name_s];
  var type = this.type;
  var iter = this.iterators[0].copy();
  if (fields.length == 1) {
    // select a key
    var field = fields[0];
    if (field == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME ||
        field === store.getKeyPath()) {
      type = ydn.db.base.QueryMethod.LIST_PRIMARY_KEY;
    } else if (!field || field == '*') {
      type = ydn.db.base.QueryMethod.LIST_VALUE;
    } else if (store.hasIndex(field)) {
      if (iter.isIndexIterator()) {
        var index_name = iter.getIndexName();
        if (field != index_name) {
          throw new ydn.debug.error.ArgumentException('select field name ' +
              'must be "' + index_name + '", but "' + field + '" found.');

        }
      } else {
        iter = new ydn.db.Iterator(iter.getStoreName(), field,
            iter.getKeyRange(), iter.isReversed(), iter.isUnique(), true);
      }
      type = ydn.db.base.QueryMethod.LIST_KEY;
    } else {
      throw new ydn.debug.error.ArgumentException('Invalid select "' +
          field + '", index not found in store "' +
          store.getName() + '"');
    }
  } else if (fields.length == 2) {
    if (!iter.isIndexIterator()) {
      throw new ydn.debug.error.ArgumentException('Only primary key can be ' +
          'selected for this query.');
    }
    for (var i = 0; i < 2; i++) {
      var is_primary = fields[i] == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME ||
          store.isKeyPath(fields[i]);
      if (!is_primary) {
        var idx_name = iter.getIndexName();
        if (fields[i] != idx_name) {
          throw new ydn.debug.error.ArgumentException('select field name ' +
              'must be "' + idx_name + '", but "' + fields[i] + '" found.');
        }
      }
    }
    type = ydn.db.base.QueryMethod.LIST_KEYS;
  } else {
    throw new ydn.debug.error.ArgumentException('Selecting more than 2 field' +
        ' names is not supported, but ' + fields.length + ' fields selected.');
  }
  return new ydn.db.Query(this.db, this.schema, iter, type);
};


/**
 * Execute query and collect as an array. This method forces query execution.
 * @param {number=} opt_limit
 * @return {!ydn.db.Request}
 */
ydn.db.Query.prototype.list = function(opt_limit) {
  var offset = 0;
  var limit = opt_limit || ydn.db.base.DEFAULT_RESULT_LIMIT;
  var type = ydn.db.base.QueryMethod.LIST_VALUE;
  if (this.type == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY ||
      this.type == ydn.db.base.QueryMethod.LIST_KEYS ||
      this.type == ydn.db.base.QueryMethod.LIST_KEY) {
    type = this.type;
  }
  // console.log(this.iterator.getState(), this.iterator.getKey());
  if (this.iterators.length == 1) {
    var basic = new ydn.db.query.Base(this.db, this.schema);
    return basic.list(type, this.iterators[0], limit, offset);
  } else {
    var jq = new ydn.db.query.Restricted(this.db, this.schema);
    return jq.list(type, this.iterators, limit);
  }
};


/**
 * Patch object.
 * @param {!Object|string|!Array.<string>} arg1 Patch object, field name or
 * field names.
 * @param {*=} opt_arg2 field value or field values.
 * @return {!ydn.db.Request}
 */
ydn.db.Query.prototype.patch = function(arg1, opt_arg2) {
  if (goog.DEBUG) {
    if (arguments.length < 1) {
      throw new ydn.debug.error.ArgumentException('too few arguments');
    } else if (arguments.length == 2) {
      if (goog.isString(arg1)) {
        // any value is OK.
      } else if (goog.isArray(arg1)) {
        if (!goog.isArray(opt_arg2)) {
          throw new ydn.debug.error.ArgumentException('an array is expected ' +
              'for second argument but, ' + ydn.json.toShortString(opt_arg2) +
              ' of type ' + typeof opt_arg2 + ' found');
        } else if (arg1.length != opt_arg2.length) {
          throw new ydn.debug.error.ArgumentException('length of two input ' +
              'arguments must be equal but, ' + arg1.length +
              ' and ' + opt_arg2.length + ' found');
        }
      }
    } else if (arguments.length == 1) {
      if (!goog.isObject(arg1)) {
        throw new ydn.debug.error.ArgumentException('an object is expected ' +
            'but, ' + ydn.json.toShortString(arg1) + ' of type ' + typeof arg1 +
            ' found');
      }
    } else {
      throw new ydn.debug.error.ArgumentException('too many arguments');
    }
  }
  var basic = new ydn.db.query.Base(this.db, this.schema);
  var req = basic.patch(this.iterators[0], arg1, opt_arg2);
  return req;
};


/**
 * Execute query and collect as an array. This method forces query execution.
 * @param {function(this: T, !ydn.db.core.req.ICursor)} cb
 * @param {T=} opt_scope
 * @return {!ydn.db.Request}
 * @template T
 */
ydn.db.Query.prototype.open = function(cb, opt_scope) {
  var basic = new ydn.db.query.Base(this.db, this.schema);
  var req = basic.open(this.iterators[0], cb, opt_scope);
  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @return {!ydn.db.Request}
 */
ydn.db.Query.prototype.count = function() {
  var basic = new ydn.db.query.Base(this.db, this.schema);
  var req = basic.count(this.iterators[0]);
  return req;
};


/**
 * Count result of query. This method forces query execution.
 * @return {!ydn.db.Request}
 */
ydn.db.Query.prototype.clear = function() {
  var basic = new ydn.db.query.Base(this.db, this.schema);
  var req = basic.clear(this.iterators[0]);
  return req;
};


ydn.db.Query.prototype.filter = function(cb, opt_scope) {
  this.filter_ = cb;
  this.filter_scope_ = opt_scope;
};


/**
 * Create a new query.
 * @param {string} store_name
 * @param {string=} opt_op1 where operator.
 * @param {IDBKey=} opt_value1 rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.Query}
 */
ydn.db.core.Storage.prototype.from = function(store_name, opt_op1, opt_value1,
                                              opt_op2, opt_value2) {
  if (goog.DEBUG && !goog.isString(store_name)) {
    throw new TypeError('store name "' + store_name + '"');
  }
  if (!this.schema.hasStore(store_name)) {
    throw new ydn.debug.error.ArgumentException('Store "' + store_name +
        '" not found.');
  }
  var range;
  if (goog.isDef(opt_op1)) {
    if (!goog.isDef(opt_value1)) {
      throw new ydn.debug.error.ArgumentException('boundary value ' +
          'must be defined.');
    }
    range = ydn.db.KeyRange.where(opt_op1, opt_value1, opt_op2, opt_value2);
  } else if (goog.isDef(opt_op2)) {
    throw new ydn.debug.error.ArgumentException('second boundary must not be' +
        ' defined.');
  }
  var iter = new ydn.db.ValueIterator(store_name, range);
  return new ydn.db.Query(this.getIndexOperator(), this.schema, iter);
};


/**
 * Create a new query.
 * @param {string} store_name
 * @param {string=} opt_op1 where operator.
 * @param {IDBKey=} opt_value1 rvalue to compare.
 * @param {string=} opt_op2 second operator.
 * @param {IDBKey=} opt_value2 second rvalue to compare.
 * @return {!ydn.db.Query}
 */
ydn.db.core.DbOperator.prototype.from = function(store_name, opt_op1,
    opt_value1, opt_op2, opt_value2) {
  if (goog.DEBUG && !goog.isString(store_name)) {
    throw new TypeError('store name "' + store_name + '"');
  }
  if (!this.schema.hasStore(store_name)) {
    throw new ydn.debug.error.ArgumentException('Store "' + store_name +
        '" not found.');
  }
  var range;
  if (goog.isDef(opt_op1)) {
    if (!goog.isDef(opt_value1)) {
      throw new ydn.debug.error.ArgumentException('boundary value ' +
          'must be defined.');
    }
    range = ydn.db.KeyRange.where(opt_op1, opt_value1, opt_op2, opt_value2);
  } else if (goog.isDef(opt_op2)) {
    throw new ydn.debug.error.ArgumentException('second boundary must not be' +
        ' defined.');
  }
  var iter = new ydn.db.ValueIterator(store_name, range);
  return new ydn.db.Query(this, this.schema, iter);
};



// Input 79
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Cursor for IndexedDB.
 */


goog.provide('ydn.db.core.req.IDBCursor');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.debug.error.InternalError');



/**
 * Open an index. This will resume depending on the cursor state.
 * @param {ydn.db.base.Transaction} tx
 * @param {string} tx_no tx no.
 * @param {ydn.db.schema.Store} store_schema schema.
 * @param {ydn.db.base.QueryMethod=} opt_mth true for keys query method.
 * @extends {ydn.db.core.req.AbstractCursor}
 * @constructor
 * @struct
 */
ydn.db.core.req.IDBCursor = function(tx, tx_no, store_schema, opt_mth) {

  goog.base(this, tx, tx_no, store_schema, opt_mth);

  /**
   * @type {IDBRequest} cursor request object.
   * @private
   */
  this.request_ = null;

};
goog.inherits(ydn.db.core.req.IDBCursor, ydn.db.core.req.AbstractCursor);


/**
 * @define {boolean} debug flag.
 */
ydn.db.core.req.IDBCursor.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.IDBCursor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.IDBCursor');


/**
 *
 * @param {Event} ev event.
 */
ydn.db.core.req.IDBCursor.prototype.defaultOnSuccess = function(ev) {
  // console.log(this, this.request_);
  var cursor = ev.target.result;
  if (ydn.db.core.req.IDBCursor.DEBUG) {
    window.console.log(this + ' onSuccess ' + (cursor ?
        cursor.key + ', ' + cursor.primaryKey : ''));
  }
  if (cursor) {
    // var p_key = this.isIndexCursor() ? cursor.primaryKey : undefined;
    this.onSuccess(cursor.key, cursor.primaryKey, cursor.value);
  } else {
    this.onSuccess();
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.openCursor = function(key, primary_key) {

  var msg = this + ' opening ';
  if (goog.isDefAndNotNull(key)) {
    msg += '{' + key;
    if (goog.isDefAndNotNull(primary_key)) {
      msg += ';' + primary_key + '}';
    } else {
      msg += '}';
    }
  }
  this.logger.finest(msg);

  var key_range = this.key_range;
  var obj_store = this.tx.objectStore(this.store_name);
  var index = goog.isString(this.index_name) ?
      obj_store.index(this.index_name) : null;
  if (goog.isDef(key)) {
    var open = index ? !(goog.isDef(primary_key)) : true;
    var s_key = /** @type {IDBKey} */ (key);
    var lower = /** @type {IDBKey|undefined} */ (key_range ?
        key_range.lower : undefined);
    var upper = /** @type {IDBKey|undefined} */ (key_range ?
        key_range.upper : undefined);
    var lowerOpen = key_range ? !!key_range.lowerOpen : false;
    var upperOpen = key_range ? !!key_range.upperOpen : false;
    var kr = this.reverse ?
        new ydn.db.KeyRange(lower, s_key, lowerOpen, open) :
        new ydn.db.KeyRange(s_key, upper, open, upperOpen);
    key_range = kr.toIDBKeyRange();
  }

  var request;
  if (!this.isValueCursor()) {
    if (index) {
      if (goog.isDefAndNotNull(this.dir)) {
        request = index.openKeyCursor(key_range, this.dir);
      } else if (goog.isDefAndNotNull(key_range)) {
        request = index.openKeyCursor(key_range);
      } else {
        request = index.openKeyCursor();
      }
    } else {
      //throw new ydn.error.InvalidOperationException(
      //    'object store cannot open for key cursor');
      // IDB v1 spec do not have openKeyCursor, hopefully next version does
      // http://lists.w3.org/Archives/Public/public-webapps/2012OctDec/0466.html
      // however, lazy serailization used at least in FF.
      if (goog.isDefAndNotNull(this.dir)) {
        request = obj_store.openCursor(key_range, this.dir);
      } else if (goog.isDefAndNotNull(key_range)) {
        request = obj_store.openCursor(key_range);
        // some browser have problem with null, even though spec said OK.
      } else {
        request = obj_store.openCursor();
      }

    }
  } else {
    if (index) {
      if (goog.isDefAndNotNull(this.dir)) {
        request = index.openCursor(key_range, this.dir);
      } else if (goog.isDefAndNotNull(key_range)) {
        request = index.openCursor(key_range);
      } else {
        request = index.openCursor();
      }
    } else {
      if (goog.isDefAndNotNull(this.dir)) {
        request = obj_store.openCursor(key_range, this.dir);
      } else if (goog.isDefAndNotNull(key_range)) {
        request = obj_store.openCursor(key_range);
        // some browser have problem with null, even though spec said OK.
      } else {
        request = obj_store.openCursor();
      }
    }
  }

  var me = this;
  request.onerror = function(ev) {
    var err = request.error;
    ev.preventDefault(); // not abort the transaction.
    me.onError(err);
  };

  /**
   *
   * @param {IDBKey=} key
   * @param {IDBKey=} primaryKey
   * @param {*=} value
   */
  var requestReady = function(key, primaryKey, value) {
    if (ydn.db.core.req.IDBCursor.DEBUG) {
      window.console.log(me + ' requestReady ' + key);
    }
    me.request_ = request;
    me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
    var p_key = me.isIndexCursor() ? primaryKey : undefined;
    me.onSuccess(key, p_key, value);
    request = null;
  };

  if (goog.isDefAndNotNull(key)) {
    // start position is given, cursor must open after this position.
    if (ydn.db.core.req.IDBCursor.DEBUG) {
      window.console.log(me + ' seeking start position, now ' + key);
    }
    request.onsuccess = function(ev) {
      var cursor = ev.target.result;
      if (cursor) {
        var cmp = ydn.db.base.indexedDb.cmp(cursor.key, key);
        var dir = me.reverse ? -1 : 1;
        if (cmp == dir) {
          requestReady(cursor.key, cursor.primaryKey, cursor.value);
        } else if (cmp == (-dir)) {
          cursor['continue'](key);
        } else {
          if (goog.isDefAndNotNull(primary_key)) {
            var cmp2 = ydn.db.base.indexedDb.cmp(
                cursor.primaryKey, primary_key);
            if (cmp2 == dir) {
              requestReady(cursor.key, cursor.primaryKey, cursor.value);
            } else {
              cursor['continue']();
            }
          } else {
            cursor['continue']();
          }
        }
      } else {
        requestReady();
      }
    };
  } else {
    me.request_ = request;
    if (ydn.db.core.req.IDBCursor.DEBUG) {
      window.console.log(me + ' ready ', me.request_);
    }
    me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.hasCursor = function() {
  return !!this.request_;
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.update = function(record) {
  var cursor = this.request_.result;
  if (cursor) {
    var df = new goog.async.Deferred();
    var req = cursor.update(record);
    req.onsuccess = function(x) {
      df.callback(x.target.result);
    };
    req.onerror = function(e) {
      e.preventDefault();
      df.errback(e);
    };
    return df;
  } else {
    throw new ydn.db.InvalidAccessError('cursor gone');
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.clear = function() {

  var cursor = this.request_.result;
  if (cursor) {
    var df = new goog.async.Deferred();
    var req = cursor['delete']();
    req.onsuccess = function(x) {
      df.callback(1);
    };
    req.onerror = function(e) {
      e.preventDefault();
      df.errback(e);
    };
    return df;
  } else {
    throw new ydn.db.InvalidAccessError('cursor gone');
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.advance = function(step) {
  var cursor = this.request_.result;

  if (step == 1) {
    //some browser, like mobile chrome does not implement cursor advance method.
    cursor['continue']();
  } else {
    cursor.advance(step);
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.continuePrimaryKey = function(key) {

  var cursor = this.request_.result;

  if (goog.DEBUG) {
    var cmp = ydn.db.base.indexedDb.cmp(key, cursor.primaryKey);
    var exp_cmp = this.reverse ? -1 : 1;
    if (cmp != exp_cmp) { // key must higher than primary key
      throw new ydn.debug.error.InternalError('continuing primary key "' + key +
          '" must ' + (this.reverse ? 'lower' : 'higher') +
          ' than current primary key "' + cursor.primaryKey + '"');
    }
  }

  var me = this;
  this.request_.onsuccess = function(ev) {
    cursor = ev.target.result;
    if (cursor) {
      cmp = ydn.db.base.indexedDb.cmp(cursor.primaryKey, key);
      if (cmp == 0 ||
          (cmp == 1 && !me.reverse) ||
          (cmp == -1 && me.reverse)) {
        me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
        var p_key = me.isIndexCursor() ? cursor.primaryKey : undefined;
        me.onSuccess(cursor.key, p_key, cursor.value);
      } else {
        cursor['continue'](); // take another step.
      }
    } else {
      me.request_.onsuccess = goog.bind(me.defaultOnSuccess, me);
      me.onSuccess();
    }
  };
  cursor['continue'](); // take one step.
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.continueEffectiveKey = function(key) {
  var cursor = this.request_.result;
  if (goog.isDefAndNotNull(key)) {
    // it is an DataError for undefined or null key.
    // this behaviour is reasonable, somehow it is not.
    cursor['continue'](key);
  } else {
    cursor['continue']();
  }
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IDBCursor.prototype.disposeInternal = function() {
  goog.base(this, 'disposeInternal');
  this.request_ = null;
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.core.req.IDBCursor.prototype.toString = function() {
    return 'IDB' + goog.base(this, 'toString');
  };
}



// Input 80
// Copyright 2005 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors.
// All Rights Reserved.

/**
 * Portions of this code are from MochiKit, received by The Closure
 * Library Authors under the MIT license. All other code is Copyright
 * 2005-2009 The Closure Library Authors. All Rights Reserved.
 */

/**
 * @fileoverview Class for tracking multiple asynchronous operations and
 * handling the results. The DeferredList object here is patterned after the
 * DeferredList object in the Twisted python networking framework.
 *
 * Based on the MochiKit code.
 *
 * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
 *
 * @author brenneman@google.com (Shawn Brenneman)
 */

goog.provide('goog.async.DeferredList');

goog.require('goog.async.Deferred');



/**
 * Constructs an object that waits on the results of multiple asynchronous
 * operations and marshals the results. It is itself a <code>Deferred</code>,
 * and may have an execution sequence of callback functions added to it. Each
 * <code>DeferredList</code> instance is single use and may be fired only once.
 *
 * The default behavior of a <code>DeferredList</code> is to wait for a success
 * or error result from every <code>Deferred</code> in its input list. Once
 * every result is available, the <code>DeferredList</code>'s execution sequence
 * is fired with a list of <code>[success, result]</code> array pairs, where
 * <code>success</code> is a boolean indicating whether <code>result</code> was
 * the product of a callback or errback. The list's completion criteria and
 * result list may be modified by setting one or more of the boolean options
 * documented below.
 *
 * <code>Deferred</code> instances passed into a <code>DeferredList</code> are
 * independent, and may have additional callbacks and errbacks added to their
 * execution sequences after they are passed as inputs to the list.
 *
 * @param {!Array.<!goog.async.Deferred>} list An array of deferred results to
 *     wait for.
 * @param {boolean=} opt_fireOnOneCallback Whether to stop waiting as soon as
 *     one input completes successfully. In this case, the
 *     <code>DeferredList</code>'s callback chain will be called with a two
 *     element array, <code>[index, result]</code>, where <code>index</code>
 *     identifies which input <code>Deferred</code> produced the successful
 *     <code>result</code>.
 * @param {boolean=} opt_fireOnOneErrback Whether to stop waiting as soon as one
 *     input reports an error. The failing result is passed to the
 *     <code>DeferredList</code>'s errback sequence.
 * @param {boolean=} opt_consumeErrors When true, any errors fired by a
 *     <code>Deferred</code> in the input list will be captured and replaced
 *     with a succeeding null result. Any callbacks added to the
 *     <code>Deferred</code> after its use in the <code>DeferredList</code> will
 *     receive null instead of the error.
 * @param {Function=} opt_canceler A function that will be called if the
 *     <code>DeferredList</code> is canceled. @see goog.async.Deferred#cancel
 * @param {Object=} opt_defaultScope The default scope to invoke callbacks or
 *     errbacks in.
 * @constructor
 * @extends {goog.async.Deferred}
 */
goog.async.DeferredList = function(
    list, opt_fireOnOneCallback, opt_fireOnOneErrback, opt_consumeErrors,
    opt_canceler, opt_defaultScope) {

  goog.base(this, opt_canceler, opt_defaultScope);

  /**
   * The list of Deferred objects to wait for.
   * @const {!Array.<!goog.async.Deferred>}
   * @private
   */
  this.list_ = list;

  /**
   * The stored return values of the Deferred objects.
   * @const {!Array}
   * @private
   */
  this.deferredResults_ = [];

  /**
   * Whether to fire on the first successful callback instead of waiting for
   * every Deferred to complete.
   * @const {boolean}
   * @private
   */
  this.fireOnOneCallback_ = !!opt_fireOnOneCallback;

  /**
   * Whether to fire on the first error result received instead of waiting for
   * every Deferred to complete.
   * @const {boolean}
   * @private
   */
  this.fireOnOneErrback_ = !!opt_fireOnOneErrback;

  /**
   * Whether to stop error propagation on the input Deferred objects. If the
   * DeferredList sees an error from one of the Deferred inputs, the error will
   * be captured, and the Deferred will be returned to success state with a null
   * return value.
   * @const {boolean}
   * @private
   */
  this.consumeErrors_ = !!opt_consumeErrors;

  /**
   * The number of input deferred objects that have fired.
   * @private {number}
   */
  this.numFinished_ = 0;

  for (var i = 0; i < list.length; i++) {
    var d = list[i];
    d.addCallbacks(goog.bind(this.handleCallback_, this, i, true),
                   goog.bind(this.handleCallback_, this, i, false));
  }

  if (list.length == 0 && !this.fireOnOneCallback_) {
    this.callback(this.deferredResults_);
  }
};
goog.inherits(goog.async.DeferredList, goog.async.Deferred);


/**
 * Registers the result from an input deferred callback or errback. The result
 * is returned and may be passed to additional handlers in the callback chain.
 *
 * @param {number} index The index of the firing deferred object in the input
 *     list.
 * @param {boolean} success Whether the result is from a callback or errback.
 * @param {*} result The result of the callback or errback.
 * @return {*} The result, to be handled by the next handler in the deferred's
 *     callback chain (if any). If consumeErrors is set, an error result is
 *     replaced with null.
 * @private
 */
goog.async.DeferredList.prototype.handleCallback_ = function(
    index, success, result) {

  this.numFinished_++;
  this.deferredResults_[index] = [success, result];

  if (!this.hasFired()) {
    if (this.fireOnOneCallback_ && success) {
      this.callback([index, result]);
    } else if (this.fireOnOneErrback_ && !success) {
      this.errback(result);
    } else if (this.numFinished_ == this.list_.length) {
      this.callback(this.deferredResults_);
    }
  }

  if (this.consumeErrors_ && !success) {
    result = null;
  }

  return result;
};


/** @override */
goog.async.DeferredList.prototype.errback = function(res) {
  goog.base(this, 'errback', res);

  // On error, cancel any pending requests.
  for (var i = 0; i < this.list_.length; i++) {
    this.list_[i].cancel();
  }
};


/**
 * Creates a <code>DeferredList</code> that gathers results from multiple
 * <code>Deferred</code> inputs. If all inputs succeed, the callback is fired
 * with the list of results as a flat array. If any input fails, the list's
 * errback is fired immediately with the offending error, and all other pending
 * inputs are canceled.
 *
 * @param {!Array.<!goog.async.Deferred>} list The list of <code>Deferred</code>
 *     inputs to wait for.
 * @return {!goog.async.Deferred} The deferred list of results from the inputs
 *     if they all succeed, or the error result of the first input to fail.
 */
goog.async.DeferredList.gatherResults = function(list) {
  return new goog.async.DeferredList(list, false, true).
      addCallback(function(results) {
        var output = [];
        for (var i = 0; i < results.length; i++) {
          output[i] = results[i][1];
        }
        return output;
      });
};

// Input 81
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview IndexedDB request executor.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.crud.req.IndexedDb');
goog.require('goog.async.DeferredList');
goog.require('goog.userAgent');
goog.require('ydn.db.crud.req.IRequestExecutor');
goog.require('ydn.db.crud.req.RequestExecutor');
goog.require('ydn.error');
goog.require('ydn.json');



/**
 * Create a new IDB request executor.
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @extends {ydn.db.crud.req.RequestExecutor}
 * @implements {ydn.db.crud.req.IRequestExecutor}
 * @struct
 */
ydn.db.crud.req.IndexedDb = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.crud.req.IndexedDb, ydn.db.crud.req.RequestExecutor);


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.crud.req.IndexedDb.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.crud.req.IndexedDb');


/**
 *
 * @const {boolean} turn on debug flag to dump debug objects.
 */
ydn.db.crud.req.IndexedDb.DEBUG = false; // always false here.


/**
 * Large number of requests can cause memory hog without increasing performance.
 * @const
 * @type {number} Maximum number of requests created per transaction.
 */
ydn.db.crud.req.IndexedDb.REQ_PER_TX = 10;


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.countStores = function(req, stores) {

  var me = this;
  var out = [];

  var count_store = function(i) {
    var table = stores[i];
    var store = req.getTx().objectStore(table);
    var request = store.count();
    request.onsuccess = function(event) {
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log(event);
      }
      out[i] = event.target.result;
      i++;
      if (i == stores.length) {
        req.setDbValue(out);
      } else {
        count_store(i);
      }

    };
    request.onerror = function(event) {
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log(event);
      }
      event.preventDefault();
      req.setDbValue(request.error, true);
    };
  };

  if (stores.length == 0) {
    req.setDbValue([]);
  } else {
    count_store(0);
  }

};


/**
 * Put objects and return list of key inserted.
 * @param {ydn.db.Request} rq request.
 * @param {boolean} is_replace true if `put`, otherwise `add`.
 * @param {boolean} is_single true if result take only the first result.
 * @param {string} store_name store name.
 * @param {!Array.<!Object>} objs object to put.
 * @param {!Array.<IDBKey>=} opt_keys optional out-of-line keys.
 */
ydn.db.crud.req.IndexedDb.prototype.insertObjects = function(rq, is_replace,
     is_single, store_name, objs, opt_keys) {

  var results = [];
  var result_count = 0;
  var has_error = false;

  var me = this;
  var mth = is_replace ? 'put' : 'add';
  var ob_store = rq.getTx().objectStore(store_name);
  var msg = rq.getLabel() + ' ' + mth + ' ' + objs.length + ' objects' +
      ' to store "' + store_name + '"';
  this.logger.finest(msg);

  var put = function(i) {

    if (!goog.isDefAndNotNull(objs[i])) {
      me.logger.finest('empty object at ' + i + ' of ' + objs.length);
      result_count++;
      if (result_count == objs.length) {
        rq.setDbValue(results, has_error);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < objs.length) {
          put(next);
        }
      }
    }

    var request;

    var obj = objs[i];
    if (goog.isDefAndNotNull(opt_keys)) {
      if (is_replace) {
        request = ob_store.put(obj, opt_keys[i]);
      } else {
        request = ob_store.add(obj, opt_keys[i]);
      }
    } else {
      if (is_replace) {
        request = ob_store.put(obj);
      } else {
        request = ob_store.add(obj);
      }
    }

    request.onsuccess = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, event, i]);
      }
      results[i] = event.target.result;
      if (result_count == objs.length) {
        rq.setDbValue(is_single ? results[0] : results, has_error);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < objs.length) {
          put(next);
        }
      }
    };

    request.onerror = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, event, i]);
      }
      var error = request.error;
      me.logger.finest(rq.getLabel() + mth + ' request to "' + store_name +
          '" cause ' + error.name + ' for object "' +
          ydn.json.toShortString(objs[i]) + '" at index ' +
          i + ' of ' + objs.length + ' objects.');
      // accessing request.error can cause InvalidStateError,
      // although it is not possible here since request has already done flag.
      // http://www.w3.org/TR/IndexedDB/#widl-IDBRequest-error
      results[i] = error;
      has_error = true;
      event.preventDefault(); // not abort the transaction.
      if (result_count == objs.length) {
        rq.setDbValue(is_single ? results[0] : results, has_error);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < objs.length) {
          put(next);
        }
      }
    };

  };

  if (objs.length > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.IndexedDb.REQ_PER_TX &&
        i < objs.length; i++) {
      put(i);
    }
  } else {
    rq.setDbValue([]);
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.putByKeys = function(rq, objs,
                                                         keys) {

  var results = [];
  var result_count = 0;
  var has_error = false;

  var out = function() {
    rq.setDbValue(results, has_error);
  };

  var me = this;

  var msg = rq.getLabel() + ' putByKeys: of ' + objs.length + ' objects';
  this.logger.finest(msg);

  var put = function(i) {
    /**
     * @type {!ydn.db.Key}
     */
    var key = keys[i];
    var store_name = key.getStoreName();
    var store = rq.getTx().objectStore(store_name);

    var request;

    if (goog.isNull(store.keyPath)) {
      request = store.put(objs[i], key.getId());
    } else {
      request = store.put(objs[i]);
    }

    request.onsuccess = function(event) {
      result_count++;
      //if (ydn.db.crud.req.IndexedDb.DEBUG) {
      //  window.console.log([store_name, event]);
      //}
      results[i] = event.target.result;
      if (result_count == objs.length) {
        out();
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < objs.length) {
          put(next);
        }
      }
    };

    request.onerror = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, event]);
      }
      var name = event.name;
      if (goog.DEBUG) {
        me.logger.warning('request result ' + name +
            ' error when put keys to "' + store_name + '" for object "' +
            ydn.json.toShortString(objs[i]) + '" at index ' +
            i + ' of ' + objs.length + ' objects.');
      }
      results[i] = request.error;
      has_error = true;
      event.preventDefault();
      if (result_count == objs.length) {
        out();
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < objs.length) {
          put(next);
        }
      }
    };

  };

  if (objs.length > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.IndexedDb.REQ_PER_TX &&
        i < objs.length; i++) {
      put(i);
    }
  } else {
    out();
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.putData = function(tx, tx_no, df,
    store_name, data, delimiter) {
  var me = this;
  var store = this.schema.getStore(store_name);
  var objectStore = tx.objectStore(store_name);
  var results = [];
  var prev_pos = data.indexOf('\n');
  var fields = data.substr(0, prev_pos).split(delimiter);
  var types = [];
  for (var j = 0; j < fields.length; j++) {
    var index = store.getIndex(fields[j]);
    if (index) {
      types[j] = index.getType();
    } else if (fields[j] == store.getKeyPath()) {
      types[j] = store.getType();
    }
  }
  prev_pos++;

  var msg = tx_no + ' Loading data ' + ' of ' + fields.length +
      '-fields record to ' + store_name;
  this.logger.finest(msg);

  var put = function() {

    var obj = {};
    var next_pos = data.indexOf('\n', prev_pos);
    var done = false;
    var text;
    if (next_pos == -1) {
      done = true;
      text = data.substring(prev_pos);
    } else {
      text = data.substring(prev_pos, next_pos);
      prev_pos = next_pos + 1;
    }

    var values = text.split(delimiter);
    for (var j = 0; j < fields.length; j++) {
      var value = values[j];
      if (types[j]) {
        if (types[j] == ydn.db.schema.DataType.TEXT) {
          value = goog.string.stripQuotes(value, '"');
        } else if (types[j] == ydn.db.schema.DataType.INTEGER) {
          value = parseInt(value, 10);
        } else if (types[j] == ydn.db.schema.DataType.NUMERIC) {
          value = parseFloat(value);
        }
      }
      obj[fields[j]] = value;
    }

    //console.log([text, obj]);

    var request = objectStore.put(obj);

    request.onsuccess = function(event) {

      //if (ydn.db.crud.req.IndexedDb.DEBUG) {
      //  window.console.log([store_name, event]);
      //}
      results.push(event.target.result);
      if (done) {
        df(results);
      } else {
        put();
      }
    };

    request.onerror = function(event) {

      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, event]);
      }
      if (goog.DEBUG && event.name == 'DataError') {
        // give useful info.
        event = new ydn.db.InvalidKeyException(store + ': ' +
            text.substring(0, 70));
      }
      event.preventDefault();
      df(request.error, true);
      // abort transaction ?
    };

  };

  put();
};


/**
* @inheritDoc
*/
ydn.db.crud.req.IndexedDb.prototype.removeById = function(req,
                                                          store_name, key) {

  var me = this;
  var store = req.getTx().objectStore(store_name);
  var msg = req.getLabel() + ' clearById: ' + store_name + ' ' + key;
  this.logger.finest(msg);

  var request = store.openCursor(ydn.db.IDBKeyRange.only(key));
  request.onsuccess = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, key, event]);
    }
    var cursor = event.target.result;
    if (cursor) {
      var r = cursor['delete']();
      r.onsuccess = function(e) {
        req.setDbValue(1);
      };
      r.onerror = function(e) {
        req.setDbValue(r.error, true);
      };
    } else {
      req.setDbValue(0);
    }

  };
  request.onerror = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, key, event]);
    }
    event.preventDefault();
    req.setDbValue(request.error, true);
  };

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.removeByKeys = function(req, keys) {

  var me = this;
  var count = 0;
  var store_name, store, key;
  var msg = req.getLabel() + ' removeByKeys: ' + keys.length + ' keys';
  this.logger.finest(msg);
  var errors = [];

  var removeAt = function(i) {
    i++;
    if (i >= keys.length) {
      var has_failed = errors.length > 0;
      if (has_failed) {
        req.setDbValue(errors, true);
      } else {
        req.setDbValue(count);
      }
      return;
    }

    if (keys[i].getStoreName() != store_name) {
      store_name = keys[i].getStoreName();
      store = req.getTx().objectStore(store_name);
    }

    var request = store['delete'](keys[i].getId());

    request.onsuccess = function(event) {
      count++;
      removeAt(i);
    };
    request.onerror = function(event) {
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, key, event]);
      }
      event.preventDefault();
      errors[i] = request.error;
      removeAt(i);
    };
  };

  removeAt(-1);

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.removeByKeyRange = function(
    req, store_name, key_range) {

  var me = this;
  var store = req.getTx().objectStore(store_name);
  var request = store.count(key_range);
  var msg = req.getLabel() + ' clearByKeyRange: ' + store_name + ' ' +
      key_range;
  this.logger.finest(msg);
  request.onsuccess = function(event) {
    var n = event.target.result;
    var r = store['delete'](key_range);
    r.onsuccess = function() {
      req.setDbValue(n);
    };
    r.onerror = function(e) {
      req.setDbValue(r.error, true);
    };
  };
  request.onerror = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, key_range, event]);
    }
    event.preventDefault();
    req.setDbValue(request.error, true);
  };

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.clearByKeyRange = function(
    req, store_name, key_range) {

  var me = this;
  var store = req.getTx().objectStore(store_name);

  var msg = req.getLabel() + ' ' + store_name + ' ' + key_range;
  this.logger.finest(msg);

  var r = store['delete'](key_range);
  r.onsuccess = function(event) {
    req.setDbValue(undefined);
  };
  r.onerror = function(event) {
    event.preventDefault();
    req.setDbValue(r.error, true);
  };

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.removeByIndexKeyRange = function(
    req, store_name, index_name, key_range) {

  var me = this;
  var store = req.getTx().objectStore(store_name);
  var index = store.index(index_name);
  var msg = req.getLabel() + ' clearByIndexKeyRange: ' + store_name + ':' +
      index_name + ' ' + key_range;
  this.logger.finest(msg);
  var errors = [];
  // var request = index.openKeyCursor(key_range);
  // theoritically key cursor should be able to delete the record, but
  // according to IndexedDB API spec, it is not.
  // if this cursor was created using openKeyCursor a DOMException of type
  // InvalidStateError is thrown.
  var request = index.openCursor(key_range);
  var n = 0;
  request.onsuccess = function(event) {
    var cursor = event.target.result;
    if (cursor) {
      //console.log(cursor);
      var r = cursor['delete']();
      r.onsuccess = function() {
        n++;
        cursor['continue']();
      };
      r.onerror = function(event) {
        errors.push(r.error);
        event.preventDefault();
        cursor['continue']();
      };
    } else {
      var has_failed = errors.length > 0;
      if (has_failed) {
        req.setDbValue(errors, true);
      } else {
        req.setDbValue(n);
      }
    }

  };
  request.onerror = function(event) {
    event.preventDefault();
    req.setDbValue(request.error, true);
  };

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.clearByStores = function(req, store_names) {

  var me = this;
  var n_todo = store_names.length;
  var n_done = 0;
  var msg = req.getLabel() + ' clearByStores: ' + store_names;
  this.logger.finest(msg);
  for (var i = 0; i < n_todo; i++) {
    var store_name = store_names[i];
    var store = req.getTx().objectStore(store_name);
    var request = store.clear();
    request.onsuccess = function(event) {
      n_done++;
      // if (ydn.db.crud.req.IndexedDb.DEBUG) {
      //   window.console.log([n_done, event]);
      // }
      if (n_done == n_todo) {
        req.setDbValue(n_done);
      }
    };
    request.onerror = function(event) {
      n_done++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([n_done, event]);
      }
      event.preventDefault();
      if (n_done == n_todo) {
        req.setDbValue(request.error, true);
      }
    };
  }
};


/**
* @inheritDoc
*/
ydn.db.crud.req.IndexedDb.prototype.getById = function(req, store_name, id) {

  var me = this;
  var msg = req.getLabel() + store_name + ':' + id;
  this.logger.finest(msg);
  var store = req.getTx().objectStore(store_name);

  var request = store.get(id);

  request.onsuccess = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, id, event]);
    }
    me.logger.finest(req.getLabel() + ' record ' + id +
        (goog.isDefAndNotNull(event.target.result) ? ' ' : ' not ') +
        ' exists.');
    // check for decode blob, since chrome does not support blob
    var BASE64_MARKER = ';base64,';
    var s = event.target.result;
    if (!store.keyPath && store.indexNames.length == 0 &&
        goog.userAgent.WEBKIT && goog.isString(s) &&
        s.indexOf(BASE64_MARKER) >= 0) {
      if (s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') {
        s = s.substr(1, s.length - 2);
      }
      var parts = s.split(BASE64_MARKER);
      var contentType = parts[0].split(':')[1];
      var raw = window.atob(parts[1]);
      var rawLength = raw.length;

      var uInt8Array = new Uint8Array(rawLength);

      for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
      }

      var blob = new Blob([uInt8Array.buffer], {type: contentType});
      req.setDbValue(blob);
    } else {
      req.setDbValue(event.target.result);
    }
  };

  request.onerror = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, id, event]);
    }
    //me.logger.warning('Error retrieving ' + id + ' in ' + store_name + ' ' +
    // event.message);
    event.preventDefault();
    req.setDbValue(request.error, true);
  };
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.listByIds = function(req,
                                                         store_name, ids) {
  var me = this;

  var results = [];
  results.length = ids.length;
  var result_count = 0;
  var store = req.getTx().objectStore(store_name);
  var n = ids.length;
  var msg = req.getLabel() + ' ' + store_name + ':' + n + ' ids';
  this.logger.finest(msg);

  var get = function(i) {

    if (!goog.isDefAndNotNull(ids[i])) {
      // should we just throw error ?
      result_count++;
      results[i] = undefined;
      if (result_count == n) {
        req.setDbValue(results);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < n) {
          get(next);
        }
      }
    }

    var request;
//    try {
    // console.log(tx_no + ': ' + store_name + ' ' + i + ' ' + ids[i])
    request = store.get(ids[i]);
//    } catch (e) {
//      if (ydn.db.crud.req.IndexedDb.DEBUG) {
//        window.console.log([store_name, i, ids[i], e]);
//        if (e.name == 'DataError') {
//          // http://www.w3.org/TR/IndexedDB/#widl-IDBObjectStore-get-
//          // IDBRequest-any-key
//          throw new ydn.db.InvalidKeyException(ids[i]);
//        } else {
//          throw e;
//        }
//      }
//    }
    request.onsuccess = (function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, ids, i, event]);
      }
      results[i] = event.target.result;
      if (result_count == n) {
        req.setDbValue(results);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < n) {
          get(next);
        }
      }
    });

    request.onerror = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([store_name, ids, i, event]);
      }
      event.preventDefault();
      req.setDbValue(request.error, true);
    };

  };

  if (n > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.IndexedDb.REQ_PER_TX && i < n; i++) {
      get(i);
    }
  } else {
    req.setDbValue([]);
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.listByKeys = function(req, keys) {
  var me = this;

  var results = [];
  results.length = keys.length;
  var result_count = 0;
  var msg = req.getLabel() + ' ' + keys.length + ' ids';
  this.logger.finest(msg);

  var getKey = function(i) {
    /**
     * @type {!ydn.db.Key}
     */
    var key = keys[i];
    /**
     * @type {IDBObjectStore}
     */
    var store = req.getTx().objectStore(key.getStoreName());
    var request = store.get(key.getId());

    request.onsuccess = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log(event);
      }
      results[i] = event.target.result;
      if (result_count == keys.length) {
        req.setDbValue(results);
      } else {
        var next = i + ydn.db.crud.req.IndexedDb.REQ_PER_TX;
        if (next < keys.length) {
          getKey(next);
        }
      }
    };

    request.onerror = function(event) {
      result_count++;
      if (ydn.db.crud.req.IndexedDb.DEBUG) {
        window.console.log([keys, event]);
      }
      event.preventDefault();
      req.setDbValue(request.error, true);
    };

  };

  if (keys.length > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.IndexedDb.REQ_PER_TX && i < keys.length;
         i++) {
      getKey(i);
    }
  } else {
    req.setDbValue([]);
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.countKeyRange = function(req,
    table, keyRange, index_name, unique) {

  if (goog.DEBUG && !!index_name && !!unique) {
    throw new ydn.error.InvalidOperationException(
        'unique count not available in IndexedDB');
  }

  var me = this;
  var store = req.getTx().objectStore(table);
  var msg = req.getLabel() + ' ' + table +
      (index_name ? ':' + index_name : '') +
      (keyRange ? ':' + ydn.json.stringify(keyRange) : '');
  this.logger.finest(msg);
  var request;
  if (goog.isDefAndNotNull(index_name)) {
    var index = store.index(index_name);
    if (goog.isDefAndNotNull(keyRange)) {
      request = index.count(keyRange);
    } else {
      request = index.count();
    }
  } else {
    if (goog.isDefAndNotNull(keyRange)) {
      request = store.count(keyRange);
    } else {
      request = store.count();
    }
  }

  request.onsuccess = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log(event);
    }
    req.setDbValue(event.target.result);
  };
  request.onerror = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log(event);
    }
    event.preventDefault();
    req.setDbValue(request.error, true);
  };

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.IndexedDb.prototype.list = function(req, type,
    store_name, index, key_range, limit, offset, reverse, unique,
    opt_position) {
  var results = [];
  var store = req.getTx().objectStore(store_name);
  var dir = ydn.db.base.getDirection(reverse, unique);
  var msg = req.getLabel() + ' ' + type + ' ' + store_name +
      (index ? ':' + index : '') +
      (key_range ? ydn.json.stringify(key_range) : '');
  if (reverse) {
    msg += ' reverse';
  }
  if (unique) {
    msg += ' unique';
  }
  if (!!opt_position && goog.isDef(opt_position[0])) {
    // start position is given, cursor must open after this position.
    var open = index ? !(goog.isDef(opt_position[1])) : true;
    var s_key = /** @type {IDBKey} */ (opt_position[0]);
    var lower = /** @type {IDBKey|undefined} */ (key_range ?
        key_range.lower : undefined);
    var upper = /** @type {IDBKey|undefined} */ (key_range ?
        key_range.upper : undefined);
    var lowerOpen = key_range ? !!key_range.lowerOpen : false;
    var upperOpen = key_range ? !!key_range.upperOpen : false;
    var kr = reverse ?
        new ydn.db.KeyRange(lower, s_key, lowerOpen, open) :
        new ydn.db.KeyRange(s_key, upper, open, upperOpen);
    key_range = kr.toIDBKeyRange();
    msg += ' starting from ' +
        ydn.json.stringify(/** @type {Object} */ (opt_position[0]));
    if (goog.isDef(opt_position[1])) {
      msg += ', ' + ydn.json.stringify(/** @type {Object} */ (opt_position[1]));
    }
  }
  this.logger.finest(msg);
  var request;
  if (type == ydn.db.base.QueryMethod.LIST_KEY ||
      type == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY ||
      type == ydn.db.base.QueryMethod.LIST_KEYS) {
    // key query
    if (index) {
      request = store.index(index).openKeyCursor(key_range, dir);
    } else {
      // NOTE: key cursor for object is not available as of IndexedDB API v1.
      request = store.openCursor(key_range, dir);
    }
  } else {
    // value query
    if (index) {
      request = store.index(index).openCursor(key_range, dir);
    } else {
      request = store.openCursor(key_range, dir);
    }
  }

  var cued = false;
  request.onsuccess = function(event) {
    /**
     *
     * @type {IDBCursorWithValue}
     */
    var cursor = event.target.result;
    if (cursor) {
      if (!cued) {
        if (offset > 0) {
          // if offset is defined, position will be ignored.
          cued = true;
          cursor.advance(offset);
          return;
        } else if (!!opt_position && !!index && goog.isDef(opt_position[0])) {
          if (goog.isDef(opt_position[1])) {
            var cmp = ydn.db.base.indexedDb.cmp(cursor.key, opt_position[0]);
            var dir = reverse ? -1 : 1;
            if (cmp == 0) {
              var cmp2 = ydn.db.base.indexedDb.cmp(
                  cursor.primaryKey, opt_position[1]);
              // console.log('continue ' + cmp2 + ' ' +
              //    cursor.key + ', ' + cursor.primaryKey);
              if (cmp2 == 0) {
                cued = true;
                // console.log('cued by primary key');
                cursor['continue'](); // skip current key
                return;
              } else if (cmp2 == dir) {
                // console.log('cued by primary key passed over');
                cued = true;
              } else {
                // console.log('continue cueing');
                cursor['continue']();
                return;
              }
            } else {
              // console.log('cued by key ' + cursor.key + ' passed over');
              cued = true;
            }
          } else {
            // console.log('cued by key passed over without primary key');
            cued = true;
          }
        } else {
          cued = true;
        }
      }
      // push to result list
      if (type == ydn.db.base.QueryMethod.LIST_KEY) {
        results.push(cursor.key);
      } else if (type == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
        results.push(cursor.primaryKey);
      } else if (type ==
          ydn.db.base.QueryMethod.LIST_KEYS) {
        var obj = {};
        if (index) {
          obj[index] = cursor.key;
        }
        if (store.keyPath) {
          obj[store.keyPath] = cursor.primaryKey;
        } else {
          obj[ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME] = cursor.primaryKey;
        }
        results.push(obj);
      } else if (type ==
          ydn.db.base.QueryMethod.LIST_VALUE) {
        results.push(cursor.value);
      } else {
        results.push([cursor.key, cursor.primaryKey, cursor.value]);
      }
      // continue
      if (results.length < limit) {
        cursor['continue']();
      } else {
        if (opt_position) {
          opt_position[0] = ydn.db.Key.clone(cursor.key);
          opt_position[1] =
              ydn.db.Key.clone(/** @type {IDBKey} */ (cursor.primaryKey));
        }
        // console.log(req + ' by limit', results);
        req.setDbValue(results);
      }
    } else {
      if (opt_position) {
        opt_position[0] = undefined;
        opt_position[1] = undefined;
      }
      // console.log(req + ' by cursor', results);
      req.setDbValue(results);
    }
  };
  request.onerror = function(event) {
    if (ydn.db.crud.req.IndexedDb.DEBUG) {
      window.console.log([store_name, event]);
    }
    event.preventDefault();
    req.setDbValue(request.error, true);
  };
};

// Input 82
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Implements request executor with IndexedDB.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.core.req.IndexedDb');
goog.require('ydn.db.algo.AbstractSolver');
goog.require('ydn.db.core.req.IDBCursor');
goog.require('ydn.db.core.req.IRequestExecutor');
goog.require('ydn.db.crud.req.IndexedDb');
goog.require('ydn.error');
goog.require('ydn.json');



/**
 * Create a new IDB request executor.
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.core.req.IRequestExecutor}
 * @extends {ydn.db.crud.req.IndexedDb}
 * @struct
 */
ydn.db.core.req.IndexedDb = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.core.req.IndexedDb, ydn.db.crud.req.IndexedDb);


/**
 *
 * @define {boolean} turn on debug flag to dump object.
 */
ydn.db.core.req.IndexedDb.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.IndexedDb.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.IndexedDb');


/**
 * @inheritDoc
 */
ydn.db.core.req.IndexedDb.prototype.getCursor = function(tx, lbl,
                                                         store_name, mth) {
  var store = this.schema.getStore(store_name);
  goog.asserts.assertObject(store, 'store "' + store_name + '" not found.');
  return new ydn.db.core.req.IDBCursor(tx, lbl, store, mth);
};


/**
 * @inheritDoc
 */
ydn.db.core.req.IndexedDb.prototype.getStreamer = function(tx, tx_no,
    store_name, index_name) {
  return new ydn.db.Streamer(tx, store_name, index_name);
};

// Input 83
/**
 * @fileoverview Mutax.
 */


goog.provide('ydn.db.base.Mutex');



/**
 * Create a new mutax with false state.
 * @constructor
 * @struct
 */
ydn.db.base.Mutex = function() {
  this.state_ = false;
};


/**
 * @type {boolean}
 * @private
 */
ydn.db.base.Mutex.prototype.state_ = false;


/**
 * Set mutex state to true.
 */
ydn.db.base.Mutex.prototype.up = function() {
  // console.log('mutex up')
  goog.asserts.assert(!this.state_, 'Mutex already up.');
  this.state_ = true;
};


/**
 * Set mutex state to false.
 */
ydn.db.base.Mutex.prototype.down = function() {
  // console.log('mutex down')
  goog.asserts.assert(this.state_, 'Mutex already down.');
  this.state_ = false;
};


/**
 * @return {boolean} mutex state.
 */
ydn.db.base.Mutex.prototype.state = function() {
  return this.state_;
};


// Input 84
/**
 * @fileoverview Cursor for simple storage mechanism.
 */


goog.provide('ydn.db.core.req.SimpleCursor');
goog.require('ydn.db.base.Mutex');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.db.core.req.ICursor');



/**
 * Open an index. This will resume depending on the cursor state.
 * @param {ydn.db.base.Transaction} tx
 * @param {string} tx_no tx no.
 * @param {!ydn.db.schema.Store} store_schema schema.
 * @param {ydn.db.base.QueryMethod=} opt_mth true for keys query method.
 * @extends {ydn.db.core.req.AbstractCursor}
 * @constructor
 */
ydn.db.core.req.SimpleCursor = function(tx, tx_no, store_schema, opt_mth) {

  goog.base(this, tx, tx_no, store_schema, opt_mth);

  goog.asserts.assert(store_schema);

  this.key_ = undefined;
  this.primary_key_ = undefined;
  this.value_ = undefined;
  this.current_ = null;
  this.onCursorComplete_ = null;
  this.result_ready_ = new ydn.db.base.Mutex();

  /**
   * @type {ydn.structs.Buffer}
   * @private
   */
  this.buffer_ = null;

  /**
   * @type {ydn.db.con.simple.Store}
   * @private
   */
  this.store_ = null;
};
goog.inherits(ydn.db.core.req.SimpleCursor, ydn.db.core.req.AbstractCursor);


/**
 * @define {boolean} for debug.
 */
ydn.db.core.req.SimpleCursor.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.SimpleCursor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.SimpleCursor');


/**
 *
 * @type {!ydn.db.schema.Store}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.store_schema;


/**
 *
 * @type {IDBKey|undefined}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.key_;


/**
 *
 * @type {IDBKey|undefined}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.primary_key_;


/**
 *
 * @type {*}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.value_;


/**
 * @type {goog.structs.AvlTree.Node}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.current_;

//
///**
// * @return {ydn.structs.Buffer}
// * @protected
// */
//ydn.db.core.req.SimpleCursor.prototype.getBuffer = function() {
//  return this.getSimpleStore().getIndexCache(this.index_name);
//};
//
//
///**
// *
// * @return {ydn.db.con.simple.Store}
// * @protected
// */
//ydn.db.core.req.SimpleCursor.prototype.getSimpleStore = function() {
//  return this.tx.getSimpleStore(this.store_name);
//};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.hasCursor = function() {
  return this.isActive();
};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.update = function(obj) {
  this.store_.addRecord(this.getPrimaryKey(), obj);
  return goog.async.Deferred.succeed();
};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.advance = function(step) {

  var me = this;
  var cnt = this.current_ ? -1 : 0;
  if (ydn.db.core.req.SimpleCursor.DEBUG) {
    var msg = this.current_ ? ' advancing ' : ' starting ';
    window.console.log(this + msg + step + ' step');
  }
  /**
   * Node traversal function.
   * @param {goog.structs.AvlTree.Node} node
   * @return {boolean|undefined} continuation.
   */
  var tr_fn = function(node) {
    cnt++;
    if (!node || cnt >= step) {
      if (ydn.db.core.req.SimpleCursor.DEBUG) {
        window.console.log('advance to ' + (node ? node.value : 'null'));
      }
      return me.defaultOnSuccess_(node);
    }
  };
  if (this.reverse) {
    this.buffer_.reverseTraverse(tr_fn, this.current_);
  } else {
    this.buffer_.traverse(tr_fn, this.current_);
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.continueEffectiveKey = function(key) {

  if (goog.isDefAndNotNull(key)) {
    var me = this;
    var start_node = new ydn.db.con.simple.Node(key);
    /**
     * Node traversal function.
     * @param {goog.structs.AvlTree.Node} node
     * @return {boolean|undefined} continuation.
     */
    var tr_fn = function(node) {
      me.current_ = node;
      if (!node) {
        return me.defaultOnSuccess_(node);
      }
      var x = /** @type {ydn.db.con.simple.Node} */ (node.value);
      var e_key = x.getKey();
      var cmp = ydn.db.cmp(e_key, key);
      if (me.reverse) {
        if (cmp != 1) {
          return me.defaultOnSuccess_(node);
        }
      } else {
        if (cmp != -1) {
          return me.defaultOnSuccess_(node);
        }
      }
    };
    if (this.reverse) {
      this.buffer_.reverseTraverse(tr_fn, start_node);
    } else {
      this.buffer_.traverse(tr_fn, start_node);
    }
  } else {
    this.advance(1);
  }

};


/**
 * @type {ydn.db.base.Mutex}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.result_ready_;


/**
 * Dispatch onSuccess callback asynchronously until result are exhausted.
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.dispatchOnSuccess_ = function() {
  var me = this;
  setTimeout(function() {
    if (me.result_ready_.state()) {
      if (ydn.db.core.req.SimpleCursor.DEBUG) {
        window.console.log(this + ' invoke success ' + me.key_);
      }
      me.result_ready_.down();
      me.onSuccess(me.key_, me.primary_key_, me.value_);
      me.dispatchOnSuccess_();
    } else {
      if (ydn.db.core.req.SimpleCursor.DEBUG) {
        window.console.log(this + ' complete');
      }
      me.onCursorComplete_();
      me.onCursorComplete_ = null;
    }
  }, 4);
};


/**
 * Node traversal function.
 * @param {goog.structs.AvlTree.Node} node
 * @return {boolean|undefined} continuation.
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.defaultOnSuccess_ = function(node) {


  this.current_ = node;

  if (node) {
    var x = /** @type {ydn.db.con.simple.Node} */ (node.value);

    // check upper bound of key range.
    if (this.key_range) {
      if (!this.reverse && goog.isDefAndNotNull(this.key_range.upper)) {
        var cmp = ydn.db.cmp(x.getKey(), this.key_range.upper);
        if (cmp == 1 || (cmp == 0 && this.key_range.upperOpen)) {
          this.current_ = null;
        }
      } else if (this.reverse && goog.isDefAndNotNull(this.key_range.lower)) {
        var cmp = ydn.db.cmp(x.getKey(), this.key_range.lower);
        if (cmp == -1 || (cmp == 0 && this.key_range.lowerOpen)) {
          this.current_ = null;
        }
      }
    }

    if (this.current_) {

      if (this.unique && goog.isDefAndNotNull(this.key_) &&
          goog.isDefAndNotNull(x.getKey())) {
        if (ydn.db.cmp(this.key_, x.getKey()) == 0) {
          return; // skip non-unique key.
        }
      }

      this.key_ = x.getKey();
      this.primary_key_ = this.is_index ? x.getPrimaryKey() : this.key_;
      if (this.query_method == ydn.db.base.QueryMethod.LIST_VALUE) {
        if (!this.isValueCursor()) {
          this.value_ = this.primary_key_;
        } else {
          goog.asserts.assert(goog.isDefAndNotNull(this.primary_key_));
          this.value_ = this.store_.getRecord(null, this.primary_key_);
        }
      }
    }
  }

  if (!this.current_) {
    this.key_ = undefined;
    this.primary_key_ = undefined;
    this.value_ = undefined;
  }

  this.result_ready_.up();

  if (ydn.db.core.req.SimpleCursor.DEBUG) {
    var key_str = this.key_ + (this.is_index ?
        ', ' + this.primary_key_ : '');
    window.console.log(this + ' new position ' + key_str + ' ' +
        this.result_ready_);
  }

  return true; // step only one traversal.
};


/**
 * @type {Function}
 * @private
 */
ydn.db.core.req.SimpleCursor.prototype.onCursorComplete_;


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.openCursor = function(
    opt_key, opt_primary_key) {

  var start_node = null;

  if (this.key_range) {
    if (this.reverse) {
      var p_key = this.is_index ? '\uffff' : undefined;
      if (goog.isDefAndNotNull(this.key_range.upper)) {
        start_node = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (this.key_range.upper), p_key);
      }
    } else {
      if (goog.isDefAndNotNull(this.key_range.lower)) {
        start_node = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (this.key_range.lower));
      }
    }
  }

  if (goog.isDefAndNotNull(opt_key)) {
    if (this.isIndexCursor()) {
      // primary key may not set. OK.
      // goog.asserts.assert(goog.isDefAndNotNull(opt_primary_key));
      start_node = new ydn.db.con.simple.Node(opt_key,
          opt_primary_key);
    } else {
      start_node = new ydn.db.con.simple.Node(opt_key);
    }
  }

  // console.log('starting from ', start_node, this.key_range, this.reverse);

  this.onCursorComplete_ = this.tx.getStorage(function(storage) {

    /**
     * @param {goog.structs.AvlTree.Node} node
     * @this {ydn.db.core.req.SimpleCursor}
     * @return {boolean|undefined} continuation.
     */
      var onSuccess = function (node) {
          /**** LGBS PATCH: FOLLOWING 4 COMMENTED OUT LINES ARE REPLACED BY NEXT 4 LINES **********/
          //var x = /** @type {ydn.db.con.simple.Node} */ (node.value);
          //var key = x.getKey();
          //// console.log('on ', x);
          //if (node && goog.isDefAndNotNull(key)) {

          var x, /** @type {ydn.db.con.simple.Node} */
                key;
          // console.log('on ', x);
          if (node && (x = (node.value)) && goog.isDefAndNotNull(key = x.getKey())) {
              if (goog.isDefAndNotNull(opt_key)) {
                  if ((ydn.db.con.simple.Node.cmp(start_node, x) == 0)) {
                      return; // skip
                  }
              } else if (this.key_range) {
                  if (!this.reverse && this.key_range.lowerOpen &&
                      goog.isDefAndNotNull(this.key_range.lower)) {
                      var cmp = ydn.db.cmp(key, this.key_range.lower);
                      if (cmp == 0) {
                          return; // skip
                      }
                  }
                  if (this.reverse && this.key_range.upperOpen &&
                      goog.isDefAndNotNull(this.key_range.upper)) {
                      var cmp = ydn.db.cmp(key, this.key_range.upper);
                      if (cmp == 0) {
                          return; // skip
                      }
                  }
              }
          }
          // console.log('ready');
          return this.defaultOnSuccess_(node);
      };

    this.store_ = storage.getSimpleStore(this.store_name);
    this.buffer_ = this.store_.getIndexCache(this.index_name);
    if (this.reverse) {
      this.buffer_.reverseTraverse(goog.bind(onSuccess, this),
          start_node);
    } else {
      this.buffer_.traverse(goog.bind(onSuccess, this),
          start_node);
    }
    this.dispatchOnSuccess_();
  }, this);

};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.clear = function() {
    //LGBS PATCH: method implementation;     //throw new ydn.debug.error.NotImplementedException();
    this.store_.removeRecord(this.getPrimaryKey());
    return goog.async.Deferred.succeed();
};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleCursor.prototype.continuePrimaryKey = function(key) {
  throw new ydn.debug.error.NotImplementedException();

};



if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.core.req.SimpleCursor.prototype.toString = function() {
    return 'Simple' + goog.base(this, 'toString');
  };
}



// Input 85
/**
 * @fileoverview basic utilities.
 */

goog.provide('ydn.db.con.simple');


/**
 * Storage key namespace.
 * @const
 * @type {string}  Storage key namespace.
 */
ydn.db.con.simple.NAMESPACE = 'ydn.db';


/**
 *
 * @const
 * @type {string} separator between tokens.
 */
ydn.db.con.simple.SEP = '^|';


/**
 * Use store name and id to form a key to use in setting key to storage.
 * @param {string} db_name database name.
 * @param {string=} opt_store_name table name.
 * @param {string=} opt_index_name table name.
 * @param {IDBKey=} opt_id id.
 * @return {string} canonical key name.
 */
ydn.db.con.simple.makeKey = function(db_name, opt_store_name, opt_index_name,
                                      opt_id) {
  var parts = [ydn.db.con.simple.NAMESPACE, db_name];
  if (goog.isDef(opt_store_name)) {
    parts.push(opt_store_name);
    if (goog.isDef(opt_index_name)) {
      parts.push(opt_index_name);
      if (goog.isDef(opt_id)) {
        parts.push(ydn.db.utils.encodeKey(opt_id));
      }
    }
  }
  return parts.join(ydn.db.con.simple.SEP);
};

// Input 86
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Node for AVL tree to hold key and primary key.
 */


goog.provide('ydn.db.con.simple.Node');



/**
 *
 * @param {!IDBKey} key must be valid IDBKey.
 * @param {IDBKey=} opt_primary_key primary key.
 * @constructor
 */
ydn.db.con.simple.Node = function(key, opt_primary_key) {

  /**
   * @final
   */
  this.key = /** @type  {!IDBKey}  */ (key);
  /**
   * @final
   */
  this.primary_key = opt_primary_key;
};


/**
 * @private
 * @type {!IDBKey}
 */
ydn.db.con.simple.Node.prototype.key;


/**
 * @private
 * @type {IDBKey|undefined}
 */
ydn.db.con.simple.Node.prototype.primary_key;


/**
 * @return {!IDBKey} effective key.
 */
ydn.db.con.simple.Node.prototype.getKey = function() {
  return this.key;
};


/**
 * @return {IDBKey|undefined} primary key.
 */
ydn.db.con.simple.Node.prototype.getPrimaryKey = function() {
  return this.primary_key;
};


if (goog.DEBUG) {
  /**
   * @override
   */
  ydn.db.con.simple.Node.prototype.toString = function() {
    return 'ydn.db.con.simple.Node(' + this.key +
        (goog.isDefAndNotNull(this.primary_key) ? ', ' +
            this.primary_key + ')' : ')');
  };
}


/**
 * Node comparator
 * @param {ydn.db.con.simple.Node} a first node.
 * @param {ydn.db.con.simple.Node} b second node.
 * @return {number} -1 if a < b, 1 if a > b, 0 if a = b.
 */
ydn.db.con.simple.Node.cmp = function(a, b) {
  var cmp = ydn.db.cmp(a.key, b.key);
  if (cmp === 0) {
    if (goog.isDefAndNotNull(a.primary_key)) {
      if (goog.isDefAndNotNull(b.primary_key)) {
        return ydn.db.cmp(a.primary_key, b.primary_key);
      } else {
        return 1;
      }
    } else if (goog.isDefAndNotNull(b.primary_key)) {
      return -1;
    } else {
      return 0;
    }
  } else {
    return cmp;
  }
};

// Input 87
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Datastructure: AvlTree.
 *
 *
 * This file provides the implementation of an AVL-Tree datastructure. The tree
 * maintains a set of unique values in a sorted order. The values can be
 * accessed efficiently in their sorted order since the tree enforces an O(logn)
 * maximum height. See http://en.wikipedia.org/wiki/Avl_tree for more detail.
 *
 * The big-O notation for all operations are below:
 * <pre>
 *   Method                 big-O
 * ----------------------------------------------------------------------------
 * - add                    O(logn)
 * - remove                 O(logn)
 * - clear                  O(1)
 * - contains               O(logn)
 * - getCount               O(1)
 * - getMinimum             O(1), or O(logn) when optional root is specified
 * - getMaximum             O(1), or O(logn) when optional root is specified
 * - getHeight              O(1)
 * - getValues              O(n)
 * - inOrderTraverse        O(logn + k), where k is number of traversed nodes
 * - reverseOrderTraverse   O(logn + k), where k is number of traversed nodes
 * </pre>
 */


goog.provide('goog.structs.AvlTree');
goog.provide('goog.structs.AvlTree.Node');

goog.require('goog.structs');
goog.require('goog.structs.Collection');



/**
 * Constructs an AVL-Tree, which uses the specified comparator to order its
 * values. The values can be accessed efficiently in their sorted order since
 * the tree enforces a O(logn) maximum height.
 *
 * @param {Function=} opt_comparator Function used to order the tree's nodes.
 * @constructor
 * @implements {goog.structs.Collection}
 */
goog.structs.AvlTree = function(opt_comparator) {
  this.comparator_ = opt_comparator ||
                     goog.structs.AvlTree.DEFAULT_COMPARATOR_;
};


/**
 * String comparison function used to compare values in the tree. This function
 * is used by default if no comparator is specified in the tree's constructor.
 *
 * @param {string} a The first string.
 * @param {string} b The second string.
 * @return {number} -1 if a < b, 1 if a > b, 0 if a = b.
 * @private
 */
goog.structs.AvlTree.DEFAULT_COMPARATOR_ = function(a, b) {
  if (String(a) < String(b)) {
    return -1;
  } else if (String(a) > String(b)) {
    return 1;
  }
  return 0;
};


/**
 * Pointer to the root node of the tree.
 *
 * @type {goog.structs.AvlTree.Node}
 * @private
 */
goog.structs.AvlTree.prototype.root_ = null;


/**
 * Comparison function used to compare values in the tree. This function should
 * take two values, a and b, and return x where:
 * <pre>
 *  x < 0 if a < b,
 *  x > 0 if a > b,
 *  x = 0 otherwise
 * </pre>
 *
 * @type {Function}
 * @private
 */
goog.structs.AvlTree.prototype.comparator_ = null;


/**
 * Pointer to the node with the smallest value in the tree.
 *
 * @type {goog.structs.AvlTree.Node}
 * @private
 */
goog.structs.AvlTree.prototype.minNode_ = null;


/**
 * Pointer to the node with the largest value in the tree.
 *
 * @type {goog.structs.AvlTree.Node}
 * @private
 */
goog.structs.AvlTree.prototype.maxNode_ = null;


/**
 * Inserts a node into the tree with the specified value if the tree does
 * not already contain a node with the specified value. If the value is
 * inserted, the tree is balanced to enforce the AVL-Tree height property.
 *
 * @param {*} value Value to insert into the tree.
 * @return {boolean} Whether value was inserted into the tree.
 * @override
 */
goog.structs.AvlTree.prototype.add = function(value) {
  // If the tree is empty, create a root node with the specified value
  if (this.root_ == null) {
    this.root_ = new goog.structs.AvlTree.Node(value);
    this.minNode_ = this.root_;
    this.maxNode_ = this.root_;
    return true;
  }

  // This will be set to the new node if a new node is added.
  var newNode = null;

  // Depth traverse the tree and insert the value if we reach a null node
  this.traverse_(function(node) {
    var retNode = null;
    if (this.comparator_(node.value, value) > 0) {
      retNode = node.left;
      if (node.left == null) {
        newNode = new goog.structs.AvlTree.Node(value, node);
        node.left = newNode;
        if (node == this.minNode_) {
          this.minNode_ = newNode;
        }
      }
    } else if (this.comparator_(node.value, value) < 0) {
      retNode = node.right;
      if (node.right == null) {
        newNode = new goog.structs.AvlTree.Node(value, node);
        node.right = newNode;
        if (node == this.maxNode_) {
          this.maxNode_ = newNode;
        }
      }
    }
    return retNode; // If null, we'll stop traversing the tree
  });

  // If a node was added, increment counts and balance tree.
  if (newNode) {
    this.traverse_(
        function(node) {
          node.count++;
          return node.parent;
        },
        newNode.parent);
    this.balance_(newNode.parent); // Maintain the AVL-tree balance
  }

  // Return true if a node was added, false otherwise
  return !!newNode;
};


/**
 * Removes a node from the tree with the specified value if the tree contains a
 * node with this value. If a node is removed the tree is balanced to enforce
 * the AVL-Tree height property. The value of the removed node is returned.
 *
 * @param {*} value Value to find and remove from the tree.
 * @return {*} The value of the removed node or null if the value was not in
 *     the tree.
 * @override
 */
goog.structs.AvlTree.prototype.remove = function(value) {
  // Assume the value is not removed and set the value when it is removed
  var retValue = null;

  // Depth traverse the tree and remove the value if we find it
  this.traverse_(function(node) {
    var retNode = null;
    if (this.comparator_(node.value, value) > 0) {
      retNode = node.left;
    } else if (this.comparator_(node.value, value) < 0) {
      retNode = node.right;
    } else {
      retValue = node.value;
      this.removeNode_(node);
    }
    return retNode; // If null, we'll stop traversing the tree
  });

  // Return the value that was removed, null if the value was not in the tree
  return retValue;
};


/**
 * Removes all nodes from the tree.
 */
goog.structs.AvlTree.prototype.clear = function() {
  this.root_ = null;
  this.minNode_ = null;
  this.maxNode_ = null;
};


/**
 * Returns true if the tree contains a node with the specified value, false
 * otherwise.
 *
 * @param {*} value Value to find in the tree.
 * @return {boolean} Whether the tree contains a node with the specified value.
 * @override
 */
goog.structs.AvlTree.prototype.contains = function(value) {
  // Assume the value is not in the tree and set this value if it is found
  var isContained = false;

  // Depth traverse the tree and set isContained if we find the node
  this.traverse_(function(node) {
    var retNode = null;
    if (this.comparator_(node.value, value) > 0) {
      retNode = node.left;
    } else if (this.comparator_(node.value, value) < 0) {
      retNode = node.right;
    } else {
      isContained = true;
    }
    return retNode; // If null, we'll stop traversing the tree
  });

  // Return true if the value is contained in the tree, false otherwise
  return isContained;
};


/**
 * Returns the number of values stored in the tree.
 *
 * @return {number} The number of values stored in the tree.
 * @override
 */
goog.structs.AvlTree.prototype.getCount = function() {
  return this.root_ ? this.root_.count : 0;
};


/**
 * Returns a k-th smallest value, based on the comparator, where 0 <= k <
 * this.getCount().
 * @param {number} k The number k.
 * @return {*} The k-th smallest value.
 */
goog.structs.AvlTree.prototype.getKthValue = function(k) {
  if (k < 0 || k >= this.getCount()) {
    return null;
  }
  return this.getKthNode_(k).value;
};


/**
 * Returns the value u, such that u is contained in the tree and u < v, for all
 * values v in the tree where v != u.
 *
 * @return {*} The minimum value contained in the tree.
 */
goog.structs.AvlTree.prototype.getMinimum = function() {
  return this.getMinNode_().value;
};


/**
 * Returns the value u, such that u is contained in the tree and u > v, for all
 * values v in the tree where v != u.
 *
 * @return {*} The maximum value contained in the tree.
 */
goog.structs.AvlTree.prototype.getMaximum = function() {
  return this.getMaxNode_().value;
};


/**
 * Returns the height of the tree (the maximum depth). This height should
 * always be <= 1.4405*(Math.log(n+2)/Math.log(2))-1.3277, where n is the
 * number of nodes in the tree.
 *
 * @return {number} The height of the tree.
 */
goog.structs.AvlTree.prototype.getHeight = function() {
  return this.root_ ? this.root_.height : 0;
};


/**
 * Inserts the values stored in the tree into a new Array and returns the Array.
 *
 * @return {Array} An array containing all of the trees values in sorted order.
 */
goog.structs.AvlTree.prototype.getValues = function() {
  var ret = [];
  this.inOrderTraverse(function(value) {
    ret.push(value);
  });
  return ret;
};


/**
 * Performs an in-order traversal of the tree and calls {@code func} with each
 * traversed node, optionally starting from the smallest node with a value >= to
 * the specified start value. The traversal ends after traversing the tree's
 * maximum node or when {@code func} returns a value that evaluates to true.
 *
 * @param {Function} func Function to call on each traversed node.
 * @param {Object=} opt_startValue If specified, traversal will begin on the
 *    node with the smallest value >= opt_startValue.
 */
goog.structs.AvlTree.prototype.inOrderTraverse =
    function(func, opt_startValue) {
  // If our tree is empty, return immediately
  if (!this.root_) {
    return;
  }

  // Depth traverse the tree to find node to begin in-order traversal from
  var startNode;
  if (opt_startValue) {
    this.traverse_(function(node) {
      var retNode = null;
      if (this.comparator_(node.value, opt_startValue) > 0) {
        retNode = node.left;
        startNode = node;
      } else if (this.comparator_(node.value, opt_startValue) < 0) {
        retNode = node.right;
      } else {
        startNode = node;
      }
      return retNode; // If null, we'll stop traversing the tree
    });
  } else {
    startNode = this.getMinNode_();
  }

  // Traverse the tree and call func on each traversed node's value
  var node = startNode, prev = startNode.left ? startNode.left : startNode;
  while (node != null) {
    if (node.left != null && node.left != prev && node.right != prev) {
      node = node.left;
    } else {
      if (node.right != prev) {
        if (func(node.value)) {
          return;
        }
      }
      var temp = node;
      node = node.right != null && node.right != prev ?
             node.right :
             node.parent;
      prev = temp;
    }
  }
};


/**
 * Performs a reverse-order traversal of the tree and calls {@code func} with
 * each traversed node, optionally starting from the largest node with a value
 * <= to the specified start value. The traversal ends after traversing the
 * tree's minimum node or when func returns a value that evaluates to true.
 *
 * @param {Function} func Function to call on each traversed node.
 * @param {Object=} opt_startValue If specified, traversal will begin on the
 *    node with the largest value <= opt_startValue.
 */
goog.structs.AvlTree.prototype.reverseOrderTraverse =
    function(func, opt_startValue) {
  // If our tree is empty, return immediately
  if (!this.root_) {
    return;
  }

  // Depth traverse the tree to find node to begin reverse-order traversal from
  var startNode;
  if (opt_startValue) {
    this.traverse_(goog.bind(function(node) {
      var retNode = null;
      if (this.comparator_(node.value, opt_startValue) > 0) {
        retNode = node.left;
      } else if (this.comparator_(node.value, opt_startValue) < 0) {
        retNode = node.right;
        startNode = node;
      } else {
        startNode = node;
      }
      return retNode; // If null, we'll stop traversing the tree
    }, this));
  } else {
    startNode = this.getMaxNode_();
  }

  // Traverse the tree and call func on each traversed node's value
  var node = startNode, prev = startNode.right ? startNode.right : startNode;
  while (node != null) {
    if (node.right != null && node.right != prev && node.left != prev) {
      node = node.right;
    } else {
      if (node.left != prev) {
        if (func(node.value)) {
          return;
        }
      }
      var temp = node;
      node = node.left != null && node.left != prev ?
             node.left :
             node.parent;
      prev = temp;
    }
  }
};


/**
 * Performs a traversal defined by the supplied {@code traversalFunc}. The first
 * call to {@code traversalFunc} is passed the root or the optionally specified
 * startNode. After that, calls {@code traversalFunc} with the node returned
 * by the previous call to {@code traversalFunc} until {@code traversalFunc}
 * returns null or the optionally specified endNode. The first call to
 * traversalFunc is passed the root or the optionally specified startNode.
 *
 * @param {Function} traversalFunc Function used to traverse the tree. Takes a
 *     node as a parameter and returns a node.
 * @param {goog.structs.AvlTree.Node=} opt_startNode The node at which the
 *     traversal begins.
 * @param {goog.structs.AvlTree.Node=} opt_endNode The node at which the
 *     traversal ends.
 * @private
 */
goog.structs.AvlTree.prototype.traverse_ =
    function(traversalFunc, opt_startNode, opt_endNode) {
  var node = opt_startNode ? opt_startNode : this.root_;
  var endNode = opt_endNode ? opt_endNode : null;
  while (node && node != endNode) {
    node = traversalFunc.call(this, node);
  }
};


/**
 * Ensures that the specified node and all its ancestors are balanced. If they
 * are not, performs left and right tree rotations to achieve a balanced
 * tree. This method assumes that at most 2 rotations are necessary to balance
 * the tree (which is true for AVL-trees that are balanced after each node is
 * added or removed).
 *
 * @param {goog.structs.AvlTree.Node} node Node to begin balance from.
 * @private
 */
goog.structs.AvlTree.prototype.balance_ = function(node) {

  this.traverse_(function(node) {
    // Calculate the left and right node's heights
    var lh = node.left ? node.left.height : 0;
    var rh = node.right ? node.right.height : 0;

    // Rotate tree rooted at this node if it is not AVL-tree balanced
    if (lh - rh > 1) {
      if (node.left.right && (!node.left.left ||
          node.left.left.height < node.left.right.height)) {
        this.leftRotate_(node.left);
      }
      this.rightRotate_(node);
    } else if (rh - lh > 1) {
      if (node.right.left && (!node.right.right ||
          node.right.right.height < node.right.left.height)) {
        this.rightRotate_(node.right);
      }
      this.leftRotate_(node);
    }

    // Recalculate the left and right node's heights
    lh = node.left ? node.left.height : 0;
    rh = node.right ? node.right.height : 0;

    // Set this node's height
    node.height = Math.max(lh, rh) + 1;

    // Traverse up tree and balance parent
    return node.parent;
  }, node);

};


/**
 * Performs a left tree rotation on the specified node.
 *
 * @param {goog.structs.AvlTree.Node} node Pivot node to rotate from.
 * @private
 */
goog.structs.AvlTree.prototype.leftRotate_ = function(node) {
  // Re-assign parent-child references for the parent of the node being removed
  if (node.isLeftChild()) {
    node.parent.left = node.right;
    node.right.parent = node.parent;
  } else if (node.isRightChild()) {
    node.parent.right = node.right;
    node.right.parent = node.parent;
  } else {
    this.root_ = node.right;
    this.root_.parent = null;
  }

  // Re-assign parent-child references for the child of the node being removed
  var temp = node.right;
  node.right = node.right.left;
  if (node.right != null) node.right.parent = node;
  temp.left = node;
  node.parent = temp;

  // Update counts.
  temp.count = node.count;
  node.count -= (temp.right ? temp.right.count : 0) + 1;
};


/**
 * Performs a right tree rotation on the specified node.
 *
 * @param {goog.structs.AvlTree.Node} node Pivot node to rotate from.
 * @private
 */
goog.structs.AvlTree.prototype.rightRotate_ = function(node) {
  // Re-assign parent-child references for the parent of the node being removed
  if (node.isLeftChild()) {
    node.parent.left = node.left;
    node.left.parent = node.parent;
  } else if (node.isRightChild()) {
    node.parent.right = node.left;
    node.left.parent = node.parent;
  } else {
    this.root_ = node.left;
    this.root_.parent = null;
  }

  // Re-assign parent-child references for the child of the node being removed
  var temp = node.left;
  node.left = node.left.right;
  if (node.left != null) node.left.parent = node;
  temp.right = node;
  node.parent = temp;

  // Update counts.
  temp.count = node.count;
  node.count -= (temp.left ? temp.left.count : 0) + 1;
};


/**
 * Removes the specified node from the tree and ensures the tree still
 * maintains the AVL-tree balance.
 *
 * @param {goog.structs.AvlTree.Node} node The node to be removed.
 * @private
 */
goog.structs.AvlTree.prototype.removeNode_ = function(node) {
  // Perform normal binary tree node removal, but balance the tree, starting
  // from where we removed the node
  if (node.left != null || node.right != null) {
    var b = null; // Node to begin balance from
    var r;        // Node to replace the node being removed
    if (node.left != null) {
      r = this.getMaxNode_(node.left);

      // Update counts.
      this.traverse_(function(node) {
        node.count--;
        return node.parent;
      }, r);

      if (r != node.left) {
        r.parent.right = r.left;
        if (r.left) r.left.parent = r.parent;
        r.left = node.left;
        r.left.parent = r;
        b = r.parent;
      }
      r.parent = node.parent;
      r.right = node.right;
      if (r.right) r.right.parent = r;
      if (node == this.maxNode_) this.maxNode_ = r;
      r.count = node.count;
    } else {
      r = this.getMinNode_(node.right);

      // Update counts.
      this.traverse_(function(node) {
        node.count--;
        return node.parent;
      }, r);

      if (r != node.right) {
        r.parent.left = r.right;
        if (r.right) r.right.parent = r.parent;
        r.right = node.right;
        r.right.parent = r;
        b = r.parent;
      }
      r.parent = node.parent;
      r.left = node.left;
      if (r.left) r.left.parent = r;
      if (node == this.minNode_) this.minNode_ = r;
      r.count = node.count;
    }

    // Update the parent of the node being removed to point to its replace
    if (node.isLeftChild()) {
      node.parent.left = r;
    } else if (node.isRightChild()) {
      node.parent.right = r;
    } else {
      this.root_ = r;
    }

    // Balance the tree
    this.balance_(b ? b : r);
  } else {
    // Update counts.
    this.traverse_(function(node) {
      node.count--;
      return node.parent;
    }, node.parent);

    // If the node is a leaf, remove it and balance starting from its parent
    if (node.isLeftChild()) {
      this.special = 1;
      node.parent.left = null;
      if (node == this.minNode_) this.minNode_ = node.parent;
      this.balance_(node.parent);
    } else if (node.isRightChild()) {
      node.parent.right = null;
      if (node == this.maxNode_) this.maxNode_ = node.parent;
      this.balance_(node.parent);
    } else {
      this.clear();
    }
  }
};


/**
 * Returns the node in the tree that has k nodes before it in an in-order
 * traversal, optionally rooted at {@code opt_rootNode}.
 *
 * @param {number} k The number of nodes before the node to be returned in an
 *     in-order traversal, where 0 <= k < root.count.
 * @param {goog.structs.AvlTree.Node=} opt_rootNode Optional root node.
 * @return {goog.structs.AvlTree.Node} The node at the specified index.
 * @private
 */
goog.structs.AvlTree.prototype.getKthNode_ = function(k, opt_rootNode) {
  var root = opt_rootNode || this.root_;
  var numNodesInLeftSubtree = root.left ? root.left.count : 0;

  if (k < numNodesInLeftSubtree) {
    return this.getKthNode_(k, root.left);
  } else if (k == numNodesInLeftSubtree) {
    return root;
  } else {
    return this.getKthNode_(k - numNodesInLeftSubtree - 1, root.right);
  }
};


/**
 * Returns the node with the smallest value in tree, optionally rooted at
 * {@code opt_rootNode}.
 *
 * @param {goog.structs.AvlTree.Node=} opt_rootNode Optional root node.
 * @return {goog.structs.AvlTree.Node} The node with the smallest value in
 *     the tree.
 * @private
 */
goog.structs.AvlTree.prototype.getMinNode_ = function(opt_rootNode) {
  if (!opt_rootNode) {
    return this.minNode_;
  }

  var minNode = opt_rootNode;
  this.traverse_(function(node) {
    var retNode = null;
    if (node.left) {
      minNode = node.left;
      retNode = node.left;
    }
    return retNode; // If null, we'll stop traversing the tree
  }, opt_rootNode);

  return minNode;
};


/**
 * Returns the node with the largest value in tree, optionally rooted at
 * opt_rootNode.
 *
 * @param {goog.structs.AvlTree.Node=} opt_rootNode Optional root node.
 * @return {goog.structs.AvlTree.Node} The node with the largest value in
 *     the tree.
 * @private
 */
goog.structs.AvlTree.prototype.getMaxNode_ = function(opt_rootNode) {
  if (!opt_rootNode) {
    return this.maxNode_;
  }

  var maxNode = opt_rootNode;
  this.traverse_(function(node) {
    var retNode = null;
    if (node.right) {
      maxNode = node.right;
      retNode = node.right;
    }
    return retNode; // If null, we'll stop traversing the tree
  }, opt_rootNode);

  return maxNode;
};



/**
 * Constructs an AVL-Tree node with the specified value. If no parent is
 * specified, the node's parent is assumed to be null. The node's height
 * defaults to 1 and its children default to null.
 *
 * @param {*} value Value to store in the node.
 * @param {goog.structs.AvlTree.Node=} opt_parent Optional parent node.
 * @constructor
 */
goog.structs.AvlTree.Node = function(value, opt_parent) {
  /**
   * The value stored by the node.
   *
   * @type {*}
   */
  this.value = value;

  /**
   * The node's parent. Null if the node is the root.
   *
   * @type {goog.structs.AvlTree.Node}
   */
  this.parent = opt_parent ? opt_parent : null;

  /**
   * The number of nodes in the subtree rooted at this node.
   *
   * @type {number}
   */
  this.count = 1;
};


/**
 * The node's left child. Null if the node does not have a left child.
 *
 * @type {goog.structs.AvlTree.Node?}
 */
goog.structs.AvlTree.Node.prototype.left = null;


/**
 * The node's right child. Null if the node does not have a right child.
 *
 * @type {goog.structs.AvlTree.Node?}
 */
goog.structs.AvlTree.Node.prototype.right = null;


/**
 * The height of the tree rooted at this node.
 *
 * @type {number}
 */
goog.structs.AvlTree.Node.prototype.height = 1;


/**
 * Returns true iff the specified node has a parent and is the right child of
 * its parent.
 *
 * @return {boolean} Whether the specified node has a parent and is the right
 *    child of its parent.
 */
goog.structs.AvlTree.Node.prototype.isRightChild = function() {
  return !!this.parent && this.parent.right == this;
};


/**
 * Returns true iff the specified node has a parent and is the left child of
 * its parent.
 *
 * @return {boolean} Whether the specified node has a parent and is the left
 *    child of its parent.
 */
goog.structs.AvlTree.Node.prototype.isLeftChild = function() {
  return !!this.parent && this.parent.left == this;
};

// Input 88
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview AVL tree suitable for async traversal and resume.
 *
 * Basically this is slight modification of @see goog.structs.AvlTree.
 * Unfortunately, the original class cannot be override since some functions
 * are not expose to superclass.
 *
 *  @suppress {accessControls}
 */


goog.provide('ydn.structs.Buffer');
goog.provide('ydn.structs.Buffer.Iterator');
goog.require('goog.structs.AvlTree');
goog.require('ydn.db.KeyRange');



/**
 *
 * @param {Function=} opt_comparator Function used to order the tree's nodes.
 * @constructor
 * @extends {goog.structs.AvlTree}
 */
ydn.structs.Buffer = function(opt_comparator) {
  goog.base(this, opt_comparator);
};
goog.inherits(ydn.structs.Buffer, goog.structs.AvlTree);


/**
 * Performs an in-order traversal of the tree and calls {@code func} with each
 * traversed node, optionally starting from the smallest node with a value >= to
 * the specified start value. The traversal ends after traversing the tree's
 * maximum node or when {@code func} returns a value that evaluates to true.
 *
 * @param {function(goog.structs.AvlTree.Node)} func Function to call on each
 * traversed node.
 * @param {Object=} opt_startValue If
 * specified, traversal will begin on the node with the smallest
 * value >= opt_startValue. If AvlTree.Node, this will start immediately
 * from the node exclusive.
 */
ydn.structs.Buffer.prototype.traverse = function(func, opt_startValue) {
  // If our tree is empty, return immediately
    if (!this.root_) {
        func(null); // let know, no more traversal -- LGBS PATCH
        return;
    }

  // Depth traverse the tree to find node to begin in-order traversal from
  var startNode;
  if (opt_startValue instanceof goog.structs.AvlTree.Node) {
    startNode = opt_startValue;
  } else if (opt_startValue) {
    this.traverse_(function(node) {
      var retNode = null;
      if (this.comparator_(node.value, opt_startValue) > 0) {
        retNode = node.left;
        startNode = node;
      } else if (this.comparator_(node.value, opt_startValue) < 0) {
        retNode = node.right;
      } else {
        startNode = node;
      }
      return retNode; // If null, we'll stop traversing the tree
    });
    if (!startNode) {
      return;
    }
  } else {
    startNode = this.getMinNode_();
  }

  // Traverse the tree and call func on each traversed node's value
  var node = startNode, prev = startNode.left ? startNode.left : startNode;
  while (node != null) {
    if (node.left != null && node.left != prev && node.right != prev) {
      node = node.left;
    } else {
      if (node.right != prev) {
        if (func(node)) {
          return;
        }
      }
      var temp = node;
      node = node.right != null && node.right != prev ?
          node.right :
          node.parent;
      prev = temp;
    }
  }
  func(null); // let know, no more traversal
};


/**
 * Performs a reverse-order traversal of the tree and calls {@code func} with
 * each traversed node, optionally starting from the largest node with a value
 * <= to the specified start value. The traversal ends after traversing the
 * tree's minimum node or when func returns a value that evaluates to true.
 *
 * @param {function(goog.structs.AvlTree.Node)} func Function to call on each
 * traversed node.
 * @param {Object=} opt_startValue If
 * specified, traversal will begin on the node with the smallest
 * value >= opt_startValue. If AvlTree.Node, this will start immediately
 * from the node exclusive.
 */
ydn.structs.Buffer.prototype.reverseTraverse = function(func, opt_startValue) {
  // If our tree is empty, return immediately
  if (!this.root_) {
    return;
  }

  // Depth traverse the tree to find node to begin reverse-order traversal from
  var startNode;
  if (opt_startValue instanceof goog.structs.AvlTree.Node) {
    startNode = opt_startValue;
  } else if (opt_startValue) {
    this.traverse_(goog.bind(function(node) {
      var retNode = null;
      if (this.comparator_(node.value, opt_startValue) > 0) {
        retNode = node.left;
      } else if (this.comparator_(node.value, opt_startValue) < 0) {
        retNode = node.right;
        startNode = node;
      } else {
        startNode = node;
      }
      return retNode; // If null, we'll stop traversing the tree
    }, this));
    if (!startNode) {
      return;
    }
  } else {
    startNode = this.getMaxNode_();
  }

  // Traverse the tree and call func on each traversed node's value
  var node = startNode, prev = startNode.right ? startNode.right : startNode;
  while (node != null) {
    if (node.right != null && node.right != prev && node.left != prev) {
      node = node.right;
    } else {
      if (node.left != prev) {
        if (func(node)) {
          return;
        }
      }
      var temp = node;
      node = node.left != null && node.left != prev ?
          node.left :
          node.parent;
      prev = temp;
    }
  }
  func(null); // let know, no more traversal
};


/**
 * Create a new iterator.
 * @param {ydn.db.KeyRange=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse iterating.
 * @param {boolean=} opt_unique unique traverse.
 * @return {!ydn.structs.Buffer.Iterator} iterator.
 */
ydn.structs.Buffer.prototype.iter = function(opt_key_range, opt_reverse,
                                        opt_unique) {
  return new ydn.structs.Buffer.Iterator(opt_key_range, opt_reverse,
      opt_unique);
};


/**
 * Key scanning.
 * @param {Function} func node visit callback.
 * @param {!ydn.structs.Buffer.Iterator|!ydn.db.KeyRange} iter iterator or key range.
 */
ydn.structs.Buffer.prototype.scan = function(func, iter) {
  var it = iter instanceof ydn.structs.Buffer.Iterator ? iter : this.iter(iter);

  // If our tree is empty, return immediately
  if (!this.root_) {
    func();
    return;
  }

  var startNode;
  var skip_first_node = false;
  if (it.current_) {
    startNode = it.current_;
  } else if (it.key_range_) {
    var start_value;
    if (it.reverse_ && goog.isDefAndNotNull(it.key_range_.upper)) {
      start_value = it.key_range_.upper;
      skip_first_node = !!it.key_range_.upperOpen;
    } else if (goog.isDefAndNotNull(it.key_range_.lower)) {
      start_value = it.key_range_.lower;
      skip_first_node = !!it.key_range_.lowerOpen;
    }
    this.traverse_(goog.bind(function(node) {
      var retNode = null;
      if (this.comparator_(node.value, start_value) > 0) {
        retNode = node.left;
      } else if (this.comparator_(node.value, start_value) < 0) {
        retNode = node.right;
        startNode = node;
      } else {
        startNode = node;
      }
      return retNode; // If null, we'll stop traversing the tree
    }, this));

  } else if (!startNode) {
    startNode = this.getMaxNode_();
  }
  if (!startNode) {
    it.current_ = null;
    func();
    return;
  }
  var node = startNode;
  var prev;
  // Traverse the tree and call func on each traversed node's value
  if (it.reverse_) {
    prev = startNode.right ? startNode.right : startNode;
    while (node != null) {
      if (node.right != null && node.right != prev && node.left != prev) {
        node = node.right;
      } else {
        if (node.left != prev) {
          it.current_ = node;
          if (skip_first_node) {
            skip_first_node = false;
            continue;
          }
          if (func(node.value)) {
            return;
          }
        }
        var temp = node;
        node = node.left != null && node.left != prev ?
            node.left :
            node.parent;
        prev = temp;
      }
    }
    it.current_ = null;
    func();
  } else {
    prev = startNode.left ? startNode.left : startNode;
    while (node != null) {
      if (node.left != null && node.left != prev && node.right != prev) {
        node = node.left;
      } else {
        if (node.right != prev) {
          it.current_ = node;
          if (skip_first_node) {
            skip_first_node = false;
            continue;
          }
          if (func(node.value)) {
            return;
          }
        }
        var temp = node;
        node = node.right != null && node.right != prev ?
            node.right :
            node.parent;
        prev = temp;
      }
    }
  }
};


/**
 * Union operation with other buffer.
 * @param {ydn.structs.Buffer} other
 */
ydn.structs.Buffer.prototype.union = function(other) {
  var this_node = this.getMinNode_();
  var that_node = other.getMinNode_();
  while (that_node) {

  }
};


/**
 * Iterator for buffer class.
 * @param {ydn.db.KeyRange=} opt_key_range key range.
 * @param {boolean=} opt_reverse reverse iterating.
 * @param {boolean=} opt_unique unique traverse.
 * @constructor
 */
ydn.structs.Buffer.Iterator = function(opt_key_range, opt_reverse, opt_unique) {
  /**
   * @final
   * @private
   */
  this.key_range_ = opt_key_range || null;
  /**
   * @final
   * @private
   */
  this.reverse_ = !!opt_reverse;
  /**
   * @final
   * @private
   */
  this.unique_ = !!opt_unique;
  this.current_ = null;
};


/**
 * Reverse traversal.
 * @type {ydn.db.KeyRange}
 * @private
 */
ydn.structs.Buffer.Iterator.prototype.key_range_ = null;


/**
 * Reverse traversal.
 * @type {boolean}
 * @private
 */
ydn.structs.Buffer.Iterator.prototype.reverse_ = false;


/**
 * Traverse unique node only.
 * @type {boolean}
 * @private
 */
ydn.structs.Buffer.Iterator.prototype.unique_ = false;


/**
 * Current node.
 * @type {goog.structs.AvlTree.Node}
 * @private
 */
ydn.structs.Buffer.Iterator.prototype.current_ = null;

// Input 89
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Object store for simple storage.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.con.simple.Store');
goog.require('ydn.db.base');
goog.require('ydn.db.con.simple');
goog.require('ydn.db.con.simple.Node');
goog.require('ydn.structs.Buffer');



/**
 *
 * @param {string} db_name database name.
 * @param {!Storage} storage
 * @param {!ydn.db.schema.Store} store_schema
 * @constructor
 */
ydn.db.con.simple.Store = function(db_name, storage, store_schema) {
  /**
   * @final
   */
  this.db_name = db_name;
  /**
   * @final
   */
  this.storage = storage;
  /**
   * @final
   */
  this.schema = store_schema;
  /**
   * @final
   */
  this.key_indexes = {};

  var kp = this.schema.getKeyPath();
  /**
   * @final
   */
  this.primary_index = goog.isArray(kp) ? kp.join(',') :
      kp || ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME;

  this.key_indexes[this.primary_index] = null;

  /**
   * @final
   */
  this.key_prefix = ydn.db.con.simple.makeKey(this.db_name,
      this.schema.getName(), this.primary_index) + ydn.db.con.simple.SEP;

};


/**
 *
 * @define {boolean} debug flag.
 */
ydn.db.con.simple.Store.DEBUG = false;


/**
 * @type {!Storage}
 * @private
 */
ydn.db.con.simple.Store.prototype.storage;


/**
 * @private
 * @type {string}
 */
ydn.db.con.simple.Store.prototype.db_name;


/**
 * @private
 * @type {!ydn.db.schema.Store}
 */
ydn.db.con.simple.Store.prototype.schema;


/**
 * @private
 * @type {string}
 */
ydn.db.con.simple.Store.prototype.primary_index;


/**
 * List of ascending ordered key for each index and primary key.
 * @type {!Object.<!ydn.structs.Buffer>}
 * @private
 */
ydn.db.con.simple.Store.prototype.key_indexes;


/**
 * Use store name and id to form a key to use in setting key to storage.
 * @protected
 * @final
 * @param {IDBKey=} opt_id id. If not given, key for store return.
 * @return {string} canonical key name.
 */
ydn.db.con.simple.Store.prototype.makeKey = function(opt_id) {
  return this.key_prefix + ydn.db.utils.encodeKey(opt_id);
};


/**
 * @type {string}
 * @private
 */
ydn.db.con.simple.Store.prototype.key_prefix;


/**
 * Extract key from encoded form.
 * @final
 * @protected
 * @param {string} eKey key as it stored in the storage.
 * @return {!IDBKey} the key.
 */
ydn.db.con.simple.Store.prototype.extractKey = function(eKey) {
  var key = ydn.db.utils.decodeKey(eKey.substr(this.key_prefix.length));
  return /** @type  {!IDBKey} */ (key);
};


/**
 * Key generator for autoIncrement key.
 * @see http://www.w3.org/TR/IndexedDB/#key-generator-concept
 * @return {!IDBKey}
 */
ydn.db.con.simple.Store.prototype.generateKey = function() {
  var store_key = this.makeKey();
  var sch = ydn.json.parse(this.storage.getItem(store_key));
  if (!sch['key_count']) {
    sch['key_count'] = 0;
  }
  sch['key_count']++;
  this.storage.setItem(store_key, ydn.json.stringify(sch));
  return sch['key_count'];
};


/**
 *
 * @param {string=} opt_index_name index name, default to primary key index.
 * @return {!ydn.structs.Buffer}
 */
ydn.db.con.simple.Store.prototype.getIndexCache = function(opt_index_name) {
  var index_name = opt_index_name || this.primary_index;
  if (!this.key_indexes[index_name]) {
    this.key_indexes[index_name] =
        new ydn.structs.Buffer(ydn.db.con.simple.Node.cmp);
    var n = this.storage.length;
    for (var i = 0; i < n; i++) {
      var key_str = this.storage.key(i);
      if (!goog.isNull(key_str)) {
        if (goog.string.startsWith(key_str, this.key_prefix)) {
          var key = this.extractKey(key_str);
          if (index_name == this.primary_index) {
            var node = new ydn.db.con.simple.Node(key);
            this.key_indexes[index_name].add(node);
          } else {
            var obj_str = this.storage.getItem(key_str);
            if (!goog.isNull(obj_str)) {
              var index = this.schema.getIndex(index_name);
              var obj = ydn.json.parse(obj_str);
              var index_key = /** @type {IDBKey} */ (index.extractKey(obj));
              if (index.isMultiEntry()) {
                if (goog.isArray(index_key)) {
                  for (var k = 0; k < index_key.length; k++) {
                    var i_node = new ydn.db.con.simple.Node(index_key[k], key);
                    this.key_indexes[index_name].add(i_node);
                  }
                }
              } else {
                var index_node = new ydn.db.con.simple.Node(index_key, key);
                this.key_indexes[index_name].add(index_node);
              }
            }
          }
        }
      }
    }
    if (ydn.db.con.simple.Store.DEBUG) {
      window.console.log('index ' + index_name + ' of ' +
          this.schema.getName() + ' for ' +
          this.key_indexes[index_name].getCount() + ' records.');
    }
  }
  return this.key_indexes[index_name];
};


/**
 *
 * @param {!IDBKey} key  primary key.
 * @param {!Object} value  record value.
 */
ydn.db.con.simple.Store.prototype.updateIndex = function(key, value) {
  for (var idx in this.key_indexes) {
    var cache = this.key_indexes[idx];
    if (cache) {
      if (ydn.db.con.simple.Store.DEBUG) {
        window.console.log('updating ' + key + ' in index ' + idx + ' of ' +
            this.schema.getName());
      }
      if (idx == this.primary_index) {
        cache.add(new ydn.db.con.simple.Node(key));
      } else {
        var index = this.schema.getIndex(idx);
        var index_key = ydn.db.utils.getValueByKeys(value, index.getKeyPath());
        if (goog.isDefAndNotNull(index_key)) {
          var node = new ydn.db.con.simple.Node(key, index_key);
          cache.add(node);
        }
      }
    }
  }
};


/**
 *
 * @param {!IDBKey} key primary key.
 * @param {!Object} value record value.
 */
ydn.db.con.simple.Store.prototype.removeIndex = function(key, value) {
  for (var idx in this.key_indexes) {
    var cache = this.key_indexes[idx];
    if (cache) {
      if (ydn.db.con.simple.Store.DEBUG) {
        window.console.log('removing ' + key + ' in index ' + idx + ' of ' +
            this.schema.getName());
      }
      if (idx == this.primary_index) {
        cache.remove(new ydn.db.con.simple.Node(key));
      } else {
        var index = this.schema.getIndex(idx);
        var index_key = ydn.db.utils.getValueByKeys(value, index.getKeyPath());
        var node = new ydn.db.con.simple.Node(key, index_key);
        cache.remove(node);
      }
    }
  }
};


/**
 * @protected
 */
ydn.db.con.simple.Store.prototype.clearIndexCache = function() {
  for (var idx in this.key_indexes) {
    var cache = this.key_indexes[idx];
    if (cache) {
      cache.clear();
      if (ydn.db.con.simple.Store.DEBUG) {
        window.console.log('index ' + idx + ' of ' +
            this.schema.getName() + ' cleared.');
      }
    }
  }
  this.key_indexes = {};
};


/**
 *
 * @param {IDBKey|undefined} key
 * @param {!Object} value
 * @param {boolean=} opt_is_add for add method, the key must not already exist.
 * @return {IDBKey?} key in case of unique key constraint, return null.
 */
ydn.db.con.simple.Store.prototype.addRecord = function(key, value, opt_is_add) {

  if (!goog.isDefAndNotNull(key)) {
    if (this.schema.usedInlineKey()) {
      key = this.schema.extractKey(value);
    }
    if (this.schema.isAutoIncrement() && !goog.isDefAndNotNull(key)) {
      key = this.generateKey();
    }
  }

  goog.asserts.assert(goog.isDefAndNotNull(key),
      this + 'primary key not provided in ' + ydn.json.toShortString(value));

  if (ydn.db.con.simple.Store.DEBUG) {
    window.console.log('add ' + key);
  }
  if (opt_is_add) {
    /*
    if (this.key_indexes[this.primary_index]) {
      var cache = this.key_indexes[this.primary_index];
      var node = new ydn.db.con.simple.Node(key);
      if (cache.contains(node)) {
        return null; // primary key constraint
      }
    } else {   */
      if (!goog.isNull(this.storage.getItem(this.makeKey(key)))) {
        return null;
      }
    // }
  }
  this.storage.setItem(this.makeKey(key),
      ydn.json.stringify(value));

  this.updateIndex(key, value);
  return key;
};


/**
 *
 * @param {IDBKey} key
 * @return {number} number deleted.
 */
ydn.db.con.simple.Store.prototype.removeRecord = function(key) {

  var eKey = this.makeKey(key);
  var obj = this.storage.getItem(eKey);

  if (goog.isNull(obj)) {
    return 0;
  } else {
    this.storage.removeItem(eKey);
    var value = ydn.json.parse(obj);
    this.removeIndex(key, value);
    return 1;
  }

};


/**
 * Clear all record in stores.
 */
ydn.db.con.simple.Store.prototype.clear = function() {
  this.clearIndexCache();
  this.removeRecords();
};


/**
 *
 * @param {string?} index_name
 * @param {!IDBKey} key
 * @return {*}
 */
ydn.db.con.simple.Store.prototype.getRecord = function(index_name, key) {
  if (!index_name || index_name == this.primary_index) {
    var v_str = this.storage.getItem(this.makeKey(key));
    var v = undefined;
    if (!goog.isNull(v_str)) {
      v = /** @type {!Object} */ (ydn.json.parse(v_str));
      for (var i = 0, n = this.schema.countIndex(); i < n; i++) {
        var index = this.schema.index(i);
        if (index.getType() == ydn.db.schema.DataType.DATE) {
          // restore date type, because after deserialization, it is a string.
          var d_str = index.extractKey(v);
          if (d_str) {
            var d = new Date(d_str);
            index.applyValue(v, d);
          }
        }
      }
    }
    return v;
  } else {
    goog.asserts.assert(this.schema.hasIndex(index_name), 'index "' +
        index_name + '" not found in ' + this);
    throw 'impl';
  }
};


/**
 *
 * @return {string} return store name.
 */
ydn.db.con.simple.Store.prototype.getName = function() {
  return this.schema.getName();
};


/**
 *
 * @param {string?=} opt_index_name index name.
 * @param {IDBKeyRange=} opt_key_range key range.
 * @return {number}
 */
ydn.db.con.simple.Store.prototype.countRecords = function(opt_index_name,
    opt_key_range) {
  opt_index_name = opt_index_name || this.primary_index;

  var me = this;
  var cache = this.getIndexCache(opt_index_name);

  /**
   * @type {ydn.db.con.simple.Node}
   */
  var start = null;
  /**
   * @type {ydn.db.con.simple.Node}
   */
  var end = null;
  var count = 0;
  var lowerOpen = false;
  var upperOpen = false;
  if (goog.isDefAndNotNull(opt_key_range)) {
    if (goog.isDefAndNotNull(opt_key_range.lower)) {
      start = new ydn.db.con.simple.Node(
          /** @type {!IDBKey} */ (opt_key_range.lower));
    }
    if (goog.isDefAndNotNull(opt_key_range.upper)) {
      end = new ydn.db.con.simple.Node(
          /** @type {!IDBKey} */ (opt_key_range.upper));
    }
    lowerOpen = opt_key_range.lowerOpen;
    upperOpen = opt_key_range.upperOpen;
  }

  /**
   *
   * @param {goog.structs.AvlTree.Node} node
   * @return {boolean|undefined}
   */
  var tr_fn = function(node) {
    if (!goog.isDefAndNotNull(node)) {
      return;
    }
    var x = /** @type {ydn.db.con.simple.Node} */ (node.value);
    if (lowerOpen && goog.isDefAndNotNull(start) &&
        ydn.db.cmp(x.getKey(), start.getKey()) == 0) {
      return;
    }
    if (goog.isDefAndNotNull(end)) {
      var cmp = ydn.db.cmp(x.getKey(), end.getKey());
      if (cmp === 1) {
        return true;
      }
      if (cmp === 0 && upperOpen) {
        return true;
      }
    }
    count++;

  };
  cache.traverse(tr_fn, start);

  return count;
};


/**
 *
 * @param {IDBKeyRange=} opt_key_range
 * @return {number}
 */
ydn.db.con.simple.Store.prototype.removeRecords = function(opt_key_range) {
  var me = this;
  var cache = this.getIndexCache(this.primary_index);
  /**
   * @type {ydn.db.con.simple.Node}
   */
  var start = null;
  /**
   * @type {ydn.db.con.simple.Node}
   */
  var end = null;
  var count = 0;
  var removed_ids = [];
  var removed_objs = [];
  var lowerOpen = false;
  var upperOpen = false;
  if (goog.isDefAndNotNull(opt_key_range)) {
    if (goog.isDefAndNotNull(opt_key_range.lower)) {
      start = new ydn.db.con.simple.Node(
          /** @type {!IDBKey} */ (opt_key_range.lower));
    }
    if (goog.isDefAndNotNull(opt_key_range.upper)) {
      end = new ydn.db.con.simple.Node(
          /** @type {!IDBKey} */ (opt_key_range.upper));
    }
    lowerOpen = opt_key_range.lowerOpen;
    upperOpen = opt_key_range.upperOpen;
  }

  /**
   *
   * @param {goog.structs.AvlTree.Node} node
   * @return {boolean|undefined}
   */
  var tr_fn = function(node) {
    if (!goog.isDefAndNotNull(node)) {
      return;
    }
    var x = /** @type {ydn.db.con.simple.Node} */ (node.value);
    if (lowerOpen && goog.isDefAndNotNull(start) &&
        ydn.db.con.simple.Node.cmp(x, start) == 0) {
      return;
    }
    if (goog.isDefAndNotNull(end)) {
      var cmp = ydn.db.con.simple.Node.cmp(x, end);
      if (cmp === 1) {
        return true;
      }
      if (cmp === 0 && upperOpen) {
        return true;
      }
    }
    var key = me.makeKey(x.getKey());
    var obj = me.storage.getItem(key);
    if (!goog.isNull(obj)) {
      me.storage.removeItem(key);
      count++;
      if (ydn.db.con.simple.Store.DEBUG) {
        window.console.log(count + '. remove ' + x.getKey() + ' ' + key);
      }
      if (removed_ids.length < 10) {
        removed_ids.push(x.getKey());
        removed_objs.push(ydn.json.parse(obj));
      }
    }

  };
  cache.traverse(tr_fn, start);

  // update tree
  if (removed_ids.length < 10) {
    for (var i = 0; i < removed_ids.length; i++) {
      this.removeIndex(removed_ids[i], removed_objs[i]);
    }
  } else {
    // to many node removed, just clear the tree.
    this.clearIndexCache();
  }

  return count;
};


/**
 *
 * @param {ydn.db.base.QueryMethod} mth
 * @param {string?=} opt_index_name
 * @param {IDBKeyRange=} opt_key_range
 * @param {boolean=} opt_reverse
 * @param {number=} opt_limit
 * @param {number=} opt_offset
 * @param {boolean=} opt_unique
 * @param {Array.<IDBKey|undefined>=} opt_position last cursor position.
 * @return {!Array} results.
 */
ydn.db.con.simple.Store.prototype.getItems = function(mth,
    opt_index_name, opt_key_range, opt_reverse, opt_limit,
    opt_offset, opt_unique, opt_position) {
  var results = [];
  var prev_key;
  var resume = !!opt_position && goog.isDefAndNotNull(opt_position[0]);
  opt_index_name = opt_index_name || this.primary_index;
  var is_index = opt_index_name != this.primary_index;
  var cache = this.getIndexCache(opt_index_name);
  /**
   * @type {ydn.db.con.simple.Node}
   */
  var start = null;
  /**
   * @type {ydn.db.con.simple.Node}
   */
  var end = null;
  if (!goog.isDef(opt_offset)) {
    opt_offset = 0;
  }
  var offsetted = -1;
  var lowerOpen = false;
  var upperOpen = false;
  if (goog.isDefAndNotNull(opt_key_range)) {
    if (goog.isDefAndNotNull(opt_key_range.lower)) {
      if (is_index && opt_reverse) {
        start = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (opt_key_range.lower), '\uffff');
      } else {
        start = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (opt_key_range.lower));
      }
    }
    if (goog.isDefAndNotNull(opt_key_range.upper)) {
      if (is_index && !opt_reverse) {
        end = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (opt_key_range.upper), '\uffff');
      } else {
        end = new ydn.db.con.simple.Node(
            /** @type {!IDBKey} */ (opt_key_range.upper));
      }
    }
    lowerOpen = !!opt_key_range.lowerOpen;
    upperOpen = !!opt_key_range.upperOpen;
  }
  if (resume) {
    if (opt_reverse) {
      upperOpen = true;
    } else {
      lowerOpen = true;
    }
    var e_key = /** @type {IDBKey} */ (opt_position[0]);
    var p_key = goog.isDef(opt_position[1]) ? opt_position[1] : '\uffff';
    if (opt_reverse) {
      if (is_index) {
        end = new ydn.db.con.simple.Node(e_key, p_key);
      } else {
        end = new ydn.db.con.simple.Node(e_key);
      }
    } else {
      if (is_index) {
        start = new ydn.db.con.simple.Node(e_key, p_key);
      } else {
        start = new ydn.db.con.simple.Node(e_key);
      }
    }
  }
  if (ydn.db.con.simple.Store.DEBUG) {
    window.console.log(this + ' ' + (opt_reverse ? ' rev' : '') +
        ' from ' + start + ' to ' + end);
  }
  // console.log(opt_reverse, start, end)
  var me = this;

  /**
   * @param {goog.structs.AvlTree.Node} node
   * @return {boolean|undefined}
   */
  var tr_fn = function(node) {
    if (!node) {
      return;
    }
    offsetted++;
    if (offsetted < opt_offset) {
      return;
    }
    var x = /** @type {ydn.db.con.simple.Node} */ (node.value);
    // console.log(x + ' ' + start + ' ' + end)
    if (opt_reverse) {
      if (upperOpen && goog.isDefAndNotNull(end)) {
        var cmp = resume ? ydn.db.con.simple.Node.cmp(x, end) :
            ydn.db.cmp(x.getKey(), end.getKey());
        if (cmp == 0) {
          return;
        }
      }
      if (goog.isDefAndNotNull(start)) {
        var cmp = resume ? ydn.db.con.simple.Node.cmp(x, start) :
            ydn.db.cmp(x.getKey(), start.getKey());
        if (cmp == -1 || (cmp == 0 && lowerOpen)) {
          if (opt_position) {
            opt_position[0] = undefined;
            opt_position[1] = undefined;
          }
          return true;
        }
      }
    } else {
      if (lowerOpen && goog.isDefAndNotNull(start)) {
        var cmp = resume ? ydn.db.con.simple.Node.cmp(x, start) :
            ydn.db.cmp(x.getKey(), start.getKey());
        if (cmp == 0) {
          return;
        }
      }
      if (goog.isDefAndNotNull(end)) {
        var cmp = resume ? ydn.db.con.simple.Node.cmp(x, end) :
            ydn.db.cmp(x.getKey(), end.getKey());
        if (cmp == 1 || (cmp == 0 && upperOpen)) {
          if (opt_position) {
            opt_position[0] = undefined;
            opt_position[1] = undefined;
          }
          return true;
        }
      }
    }

    var key = x.getKey();

    // console.log(prev_key, key)

    if (!opt_unique || !is_index ||
        !goog.isDefAndNotNull(prev_key) ||
        ydn.db.cmp(prev_key, key) != 0) {
      // console.log('take')
      var primary_key = /** @type {!IDBKey} */ (is_index ?
          x.getPrimaryKey() : key);

      if (mth == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
        results.push(primary_key);
      } else if (mth == ydn.db.base.QueryMethod.LIST_KEY) {
        results.push(key);
      } else if (mth == ydn.db.base.QueryMethod.LIST_KEYS) {
        results.push([key, primary_key]);
      } else if (mth == ydn.db.base.QueryMethod.LIST_VALUE) {
        var v = me.getRecord(null, primary_key);
        results.push(v);
      } else {
        results.push([key, primary_key, me.getRecord(null, primary_key)]);
      }
      if (opt_position) {
        opt_position[0] = key;
        opt_position[1] = primary_key;
      }
    }


    prev_key = key;
    if (goog.isDef(opt_limit) && results.length >= opt_limit) {
      return true;
    }
  };

  if (opt_reverse) {
    cache.reverseTraverse(tr_fn, end);
  } else {
    cache.traverse(tr_fn, start);
  }
  return results;
};


/**
 *
 * @param {string?=} opt_index_name
 * @param {IDBKeyRange=} opt_key_range
 * @param {boolean=} opt_reverse
 * @param {number=} opt_limit
 * @param {number=} opt_offset
 * @return {!Array} results.
 */
ydn.db.con.simple.Store.prototype.getRecords = function(opt_index_name,
    opt_key_range, opt_reverse, opt_limit, opt_offset) {
  return this.getItems(ydn.db.base.QueryMethod.LIST_VALUE, opt_index_name,
      opt_key_range, opt_reverse, opt_limit, opt_offset);
};


/**
 *
 * @param {string?=} opt_index_name
 * @param {IDBKeyRange=} opt_key_range
 * @param {boolean=} opt_reverse
 * @param {number=} opt_limit
 * @param {number=} opt_offset
 * @return {!Array} results.
 */
ydn.db.con.simple.Store.prototype.getKeys = function(opt_index_name,
    opt_key_range, opt_reverse, opt_limit, opt_offset) {
  return this.getItems(ydn.db.base.QueryMethod.LIST_PRIMARY_KEY, opt_index_name,
      opt_key_range, opt_reverse, opt_limit, opt_offset);
};


if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.con.simple.Store.prototype.toString = function() {
    return 'ydn.db.con.simple.Store:' + this.db_name + ':' +
        this.schema.getName();
  };
}



// Input 90
/**
 * @fileoverview Provider for WebStorage like storage.
 */


goog.provide('ydn.db.con.simple.IStorageProvider');



/**
 * @interface
 */
ydn.db.con.simple.IStorageProvider = function() {};


/**
 * @param {string} db_name
 * @return {!Storage}
 */
ydn.db.con.simple.IStorageProvider.prototype.connectDb = function (db_name) {};
// Input 91
/**
 * @fileoverview Same interface as localStorage, but store in memory.
 */

goog.provide('ydn.db.req.InMemoryStorage');
goog.require('ydn.db.con.simple.IStorageProvider');



/**
 * Implements for storage.
 * http://dev.w3.org/html5/webstorage/#storage-0
 * @implements {Storage}
 * @implements {ydn.db.con.simple.IStorageProvider}
 * @constructor
 */
ydn.db.req.InMemoryStorage = function() {
  this.clear();
};


/**
 * @inheritDoc
 */
ydn.db.req.InMemoryStorage.prototype.connectDb = function(name) {
  return this;
};


/**
 *
 * @param {string} key key.
 * @param {string} value value.
 */
ydn.db.req.InMemoryStorage.prototype.setItem = function(key, value) {
  if (!goog.isDef(this.memoryStorage[key])) {
    this.keys.push(key.toString());
    this.length = this.keys.length;
  }
  this.memoryStorage[key] = value;
};


/**
 *
 * @param {string} key key.
 * @return {string?} value. If not found, null is return.
 */
ydn.db.req.InMemoryStorage.prototype.getItem = function(key) {
  if (!goog.isDef(this.memoryStorage[key])) {
    // window.localStorage return null if the key don't exist.
    return null;
  } else {
    return this.memoryStorage[key];
  }
};


/**
 *
 * @param {string} key key.
 */
ydn.db.req.InMemoryStorage.prototype.removeItem = function(key) {
  delete this.memoryStorage[key];
  goog.array.remove(this.keys, key.toString());
  this.length = this.keys.length;
};


/**
 *
 * @type {number} return the number of key/value pairs currently present in
 * the list associated with the object. null if not found.
 */
ydn.db.req.InMemoryStorage.prototype.length = 0;


/**
 *
 * @param {number} i
 * @return {string?}  return the name of the nth key in the list.
 */
ydn.db.req.InMemoryStorage.prototype.key = function(i) {

  var key = this.keys[i];

  return goog.isDef(key) ? this.memoryStorage[key] : null;
};


/**
 * Clear all cache.
 */
ydn.db.req.InMemoryStorage.prototype.clear = function() {
  this.memoryStorage = {};
  this.keys = [];
  this.length = 0;
};

// Input 92
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Data store in memory.
 */


goog.provide('ydn.db.con.SimpleStorageService');
goog.require('goog.async.Deferred');
goog.require('ydn.db.con.simple.IStorageProvider');
goog.require('ydn.db.con.simple.Store');
goog.require('ydn.db.req.InMemoryStorage');
goog.require('ydn.debug.error.InternalError');



/**
 * @param {!ydn.db.con.simple.IStorageProvider=} opt_provider storage provider.
 * @constructor
 * @struct
 */
ydn.db.con.SimpleStorageService = function(opt_provider) {

  /**
   * @final
   * @private
   */
  this.provider_ = opt_provider || new ydn.db.req.InMemoryStorage();

  /**
   * @protected
   * @type {string}
   */
  this.dbname;

  /**
   * @type {ydn.db.con.simple.IStorageProvider}
   */
  this.provider_;

  /**
   * @type {!Storage}
   */
  this.storage_;

  /**
   * @protected
   * @type {!ydn.db.schema.Database}
   */
  this.schema;

  /**
   * @type {Object.<!ydn.db.con.simple.Store>}
   * @private
   */
  this.simple_stores_ = {};
};


/**
 * Column name of key, if keyPath is not specified.
 * @const {string}
 */
ydn.db.con.SimpleStorageService.DEFAULT_KEY_PATH = '_id_';


/**
 * @param {string} store_name store name.
 * @return {!ydn.db.con.simple.Store} storage object.
 */
ydn.db.con.SimpleStorageService.prototype.getSimpleStore = function(store_name) {
  var store = this.schema.getStore(store_name);
  if (store) {
    if (!this.simple_stores_[store_name]) {
      this.simple_stores_[store_name] =
          new ydn.db.con.simple.Store(this.dbname, this.storage_, store);
    }
  } else {
    throw new ydn.debug.error.InternalError('store name "' + store_name +
        '" not found.');
  }
  return this.simple_stores_[store_name];
};


/**
 *
 * @param {function(ydn.db.schema.Database)} callback database schema obtained
 * by reflecting connected database.
 */
ydn.db.con.SimpleStorageService.prototype.getSchema = function(callback) {
  var me = this;
  setTimeout(function() {
    var db_key = ydn.db.con.simple.makeKey(me.dbname);
    var db_value = me.storage_.getItem(db_key);
    var schema = new ydn.db.schema.Database(db_value);
    callback(schema);
  }, 10);
};

// Input 93
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Transactional simple storage.
 */


goog.provide('ydn.db.con.simple.TxStorage');
goog.require('ydn.db.con.SimpleStorageService');



/**
 *
 * @param {!ydn.db.con.SimpleStorageService} storage parent storage.
 * @param {function(ydn.db.base.TxEventTypes, *)} oncompleted function.
 * @constructor
 * @struct
 */
ydn.db.con.simple.TxStorage = function(storage, oncompleted) {
  /**
   * @private
   * @type {ydn.db.con.SimpleStorageService}
   */
  this.storage_ = storage;
  /**
   * @final
   * @private
   */
  this.on_completed_ = oncompleted;
};


/**
 * @type {function(ydn.db.base.TxEventTypes, *)?}
 * @private
 */
ydn.db.con.simple.TxStorage.prototype.on_completed_;


/**
 * @param {function(this: T, !ydn.db.con.SimpleStorageService)} fnc storage.
 * @param {T} fnc_obj function object handle.
 * @return {Function} on_complete function to invoke after complete.
 * @template T
 */
ydn.db.con.simple.TxStorage.prototype.getStorage = function(
    fnc, fnc_obj) {
  var st = this.storage_;
  goog.asserts.assertObject(st, 'transaction already committed');
  setTimeout(function() {
    fnc.call(fnc_obj, /** @type {!ydn.db.con.SimpleStorageService} */ (st));
  }, 4);

  var me = this;
  var on_complete = function() {
    me.on_completed_(ydn.db.base.TxEventTypes.COMPLETE, null);
    me.on_completed_ = null;
    me.storage_ = null;
  };
  return on_complete;
};


// Input 94
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Data store in memory.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */



goog.provide('ydn.db.crud.req.SimpleStore');
goog.require('goog.asserts');
goog.require('goog.async.Deferred');
goog.require('ydn.db.ConstraintError');
goog.require('ydn.db.con.simple.Store');
goog.require('ydn.db.con.simple.TxStorage');
goog.require('ydn.db.crud.req.IRequestExecutor');
goog.require('ydn.db.crud.req.RequestExecutor');



/**
 * @extends {ydn.db.crud.req.RequestExecutor}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.crud.req.IRequestExecutor}
 * @struct
 */
ydn.db.crud.req.SimpleStore = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.crud.req.SimpleStore, ydn.db.crud.req.RequestExecutor);


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.crud.req.SimpleStore.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.crud.req.SimpleStore');


/**
 *
 * @define {boolean} use sync result.
 */
ydn.db.crud.req.SimpleStore.SYNC = true;


/**
 *
 * @type {boolean} debug flag. should always be false.
 */
ydn.db.crud.req.SimpleStore.DEBUG = false;


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.putByKeys = function(req, objs,
                                                           keys) {
  this.insertObjects(req, true, false, null, objs, keys);
};


/**
 * Put objects and return list of key inserted.
 * @param {ydn.db.Request} req request.
 * @param {boolean} is_update true if `put`, otherwise `add`.
 * @param {boolean} single true if result take only the first result.
 * @param {string?} store_name store name.
 * @param {!Array.<!Object>} value object to put.
 * @param {!Array.<IDBKey|ydn.db.Key>=} opt_key optional out-of-line keys.
 */
ydn.db.crud.req.SimpleStore.prototype.insertObjects = function(req,
    is_update, single, store_name, value, opt_key) {

  var label = req.getLabel() + ' ' + (is_update ? 'put' : 'add') +
      'Object' + (single ? '' : 's ' + value.length + ' objects');

  this.logger.finest(label);
  var me = this;

  var tx = /** @type {ydn.db.con.simple.TxStorage} */ (req.getTx());
  var on_comp = tx.getStorage(function(storage) {
    var store;
    if (single) {
      goog.asserts.assertString(store_name, 'store name must be provided');
      store = storage.getSimpleStore(store_name);
      var key = /** @type {IDBKey|undefined} */ (opt_key ?
          opt_key[0] : undefined);
      key = store.addRecord(key, value[0], !is_update);
      if (goog.isDefAndNotNull(key)) {
        req.setDbValue(key);
      } else {
        var msg = goog.DEBUG ? ydn.json.toShortString(key) : '';
        var e = new ydn.db.ConstraintError(msg);
        req.setDbValue(e, true);
      }
    } else {
      var st = store_name;
      var arr = [];
      var has_error = false;
      var keys = opt_key || {};
      for (var i = 0; i < value.length; i++) {
        var id;
        if (!store_name) {
          /**
           * @type {ydn.db.Key}
           */
          var db_key = /** @type {ydn.db.Key} */ (opt_key[i]);
          id = db_key.getId();
          st = db_key.getStoreName();
        } else {
          id = keys[i];
        }
        if (!store || store.getName() != st) {
          goog.asserts.assertString(st, 'store name a string, but ' + st);
          store = storage.getSimpleStore(st);
        }
        var result_key = store.addRecord(id, value[i], !is_update);
        if (!goog.isDefAndNotNull(result_key)) {
          has_error = true;
          arr.push(new ydn.db.ConstraintError());
        } else {
          arr.push(result_key);
        }
      }
      req.setDbValue(arr, has_error);
    }
    on_comp();
    on_comp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.putData = function(tx, tx_no, df,
    store_name, data, delimiter) {
  throw new ydn.debug.error.NotImplementedException('putData');
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.getById = function(req, store_name, id) {
  var onComp = req.getTx().getStorage(function(storage) {
    /**
     * @type  {!ydn.db.con.simple.Store}
     */
    var store = storage.getSimpleStore(store_name);
    var key = store.getRecord(null, id);
    req.setDbValue(key);
    onComp();
    onComp = null;
  }, this);
};


/**
 *
 * @param {ydn.db.Request} req request.
 * @param {string?} store_name table name.
 * @param {!Array.<(IDBKey|!ydn.db.Key)>} ids id to get.
 * @private
 */
ydn.db.crud.req.SimpleStore.prototype.listByIds_ = function(req,
                                                            store_name, ids) {
  var onComp = req.getTx().getStorage(function(storage) {
    var arr = [];
    var has_error = false;
    var st = store_name;
    /**
     * @type  {!ydn.db.con.simple.Store}
     */
    var store;

    for (var i = 0; i < ids.length; i++) {
      var id = ids[i];
      if (id instanceof ydn.db.Key) {
        /**
         * @type {ydn.db.Key}
         */
        var db_key = id;
        id = db_key.getId();
        st = db_key.getStoreName();
      }
      if (!store || store.getName() != st) {
        store = storage.getSimpleStore(st);
      }
      var value = store.getRecord(null, id);
      // if (!goog.isDefAndNotNull(value)) {
      //  has_error = true;
      // }
      arr[i] = value;
    }

    req.setDbValue(arr, has_error);
    onComp();
    onComp = null;
  }, this);
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.listByIds = function(req,
                                                           store_name, ids) {
  this.listByIds_(req, store_name, ids);
};


/**
* @inheritDoc
*/
ydn.db.crud.req.SimpleStore.prototype.listByKeys = function(req, keys) {
  this.listByIds_(req, null, keys);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.removeById = function(req,
                                                            store_name, id) {
  var msg = req.getLabel() + ' removeById ' + store_name + ' ' + id;
  this.logger.finest(msg);
  var me = this;
  var onComp = req.getTx().getStorage(function(storage) {
    var store = storage.getSimpleStore(store_name);
    var cnt = store.removeRecord(id);
    me.logger.finer('success ' + msg + (cnt == 0 ? ' [not found]' : ''));
    req.setDbValue(cnt);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.removeByKeys = function(req, keys) {
  var msg = req.getLabel() + ' removeByKeys ' + keys.length + ' keys';
  this.logger.finest(msg);
  var me = this;
  var store;
  var deleted = 0;
  var onComp = req.getTx().getStorage(function(storage) {
    for (var i = 0; i < keys.length; i++) {
      var store_name = keys[i].getStoreName();
      var id = keys[i].getId();
      if (!store || store.getName() != store_name) {
        store = storage.getSimpleStore(store_name);
      }
      deleted += store.removeRecord(id);
    }
    req.setDbValue(deleted);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.clearByKeyRange = function(
    req, store_name, key_range) {
  this.removeByKeyRange(req, store_name, key_range);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.removeByKeyRange = function(
    req, store_name, key_range) {
  var msg = req.getLabel() + ' removeByKeyRange ' +
      (key_range ? ydn.json.stringify(key_range) : '');
  this.logger.finest(msg);
  var me = this;
  var onComp = req.getTx().getStorage(function(storage) {
    var store = storage.getSimpleStore(store_name);
    var cnt = store.removeRecords(key_range);
    me.logger.finer(msg + ' deleted ' + cnt + ' records.');
    req.setDbValue(cnt);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.removeByIndexKeyRange = function(
    req, store_name, index_name, key_range) {
  var msg = req.getLabel() + ' removeByIndexKeyRange ' +
      (key_range ? ydn.json.stringify(key_range) : '');
  this.logger.finest(msg);
  var me = this;
  var onComp = req.getTx().getStorage(function(storage) {
    var store = storage.getSimpleStore(store_name);
    var keys = store.getKeys(index_name, key_range);
    var cnt = keys.length;
    for (var i = 0; i < cnt; i++) {
      store.removeRecord(keys[i]);
    }
    req.setDbValue(cnt);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
*/
ydn.db.crud.req.SimpleStore.prototype.clearByStores = function(req,
                                                               store_names) {
  var msg = req.getLabel() + ' clearByStores';
  this.logger.finest(msg);
  var onComp = req.getTx().getStorage(function(storage) {
    for (var i = 0; i < store_names.length; i++) {
      var store = storage.getSimpleStore(store_names[i]);
      store.clear();
    }
    this.logger.finer('success ' + msg);
    req.setDbValue(store_names.length);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.countStores = function(req, store_names) {
  var onComp = req.getTx().getStorage(function(storage) {
    var arr = [];
    for (var i = 0; i < store_names.length; i++) {
      var store = storage.getSimpleStore(store_names[i]);
      arr.push(store.countRecords());
    }
    req.setDbValue(arr);
    onComp();
    onComp = null;
  }, this);

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.countKeyRange = function(req,
    store_name, keyRange, index_name) {
  var msg = req.getLabel() + ' count' +
      (goog.isDefAndNotNull(index_name) ? 'Index' : '') +
      (goog.isDefAndNotNull(keyRange) ? 'KeyRange' : 'Store');
  this.logger.finest(msg);
  var onComp = req.getTx().getStorage(function(storage) {
    var store = storage.getSimpleStore(store_name);
    var no = store.countRecords(index_name, keyRange);
    this.logger.finer('success ' + msg);
    req.setDbValue(no);
    onComp();
    onComp = null;
  }, this);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.SimpleStore.prototype.list = function(req, type, store_name,
    index, key_range, limit, offset, reverse, unique, opt_position) {
  var msg = req.getLabel() + ' ' + store_name + ' ' +
      (key_range ? ydn.json.toShortString(key_range) : '');
  this.logger.finest(msg);
  var onComp = req.getTx().getStorage(function(storage) {
    var store = storage.getSimpleStore(store_name);
    var results = store.getItems(type, index, key_range,
        reverse, limit, offset, unique, opt_position);
    this.logger.finer(msg + ' ' + results.length + ' records found.');
    req.setDbValue(results);
    onComp();
    onComp = null;
  }, this);
};

// Input 95
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Data store in memory.
 */

goog.provide('ydn.db.core.req.SimpleStore');
goog.require('ydn.db.core.req.IRequestExecutor');
goog.require('ydn.db.core.req.SimpleCursor');
goog.require('ydn.db.crud.req.SimpleStore');



/**
 * @extends {ydn.db.crud.req.SimpleStore}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.core.req.IRequestExecutor}
 */
ydn.db.core.req.SimpleStore = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.core.req.SimpleStore, ydn.db.crud.req.SimpleStore);


/**
 * @define {boolean} debug flag.
 */
ydn.db.core.req.SimpleStore.DEBUG = false;


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleStore.prototype.getCursor = function(tx, lbl,
                                                           store_name, mth) {
  var store = this.schema.getStore(store_name);
  goog.asserts.assertObject(store, 'store "' + store_name + '" not found.');
  return new ydn.db.core.req.SimpleCursor(tx, lbl, store, mth);
};


/**
 * @inheritDoc
 */
ydn.db.core.req.SimpleStore.prototype.getStreamer =  function(tx, tx_no,
    store_name, index_name) {
  throw 'not yet';
};



// Input 96
/**
 * @fileoverview Cursor.
 */


goog.provide('ydn.db.core.req.CachedWebsqlCursor');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.db.core.req.ICursor');


// ? it seems release properly at least in chrome.

/**
 * Open an index. This will resume depending on the cursor state.
 * @param {ydn.db.base.Transaction} tx
 * @param {string} tx_no tx no
 * @param {!ydn.db.schema.Store} store_schema schema.
 * @param {ydn.db.base.QueryMethod=} key_query true for keys query
 * method.
 * @extends {ydn.db.core.req.AbstractCursor}
 * @implements {ydn.db.core.req.ICursor}
 * @constructor
 */
ydn.db.core.req.CachedWebsqlCursor = function(tx, tx_no, store_schema, key_query) {

  goog.base(this, tx, tx_no, store_schema, key_query);


  goog.asserts.assert(store_schema);
  this.store_schema_ = store_schema;

  this.cursor_ = null;
  this.current_cursor_index_ = NaN;
  this.has_pending_request = false;

  //this.openCursor(ini_key, ini_index_key);
};
goog.inherits(ydn.db.core.req.CachedWebsqlCursor,
    ydn.db.core.req.AbstractCursor);


/**
 * @define {boolean} debug flag.
 */
ydn.db.core.req.CachedWebsqlCursor.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.CachedWebsqlCursor');


/**
 *
 * @type {!ydn.db.schema.Store}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.store_schema_;


/**
 *
 * @type {*}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.current_key_ = null;

/**
 *
 * @type {*}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.current_primary_key_ = null;

/**
 *
 * @type {*}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.current_value_ = null;


/**
 * @type {SQLResultSet}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.cursor_ = null;

/**
 *
 * @type {number}
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.current_cursor_index_ = NaN;


/**
 * Move cursor to next position.
 * @return {Array} [primary_key, effective_key, reference_value]
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.moveNext_ = function() {

  this.current_cursor_index_++;

  return [this.getPrimaryKey(), this.getIndexKey(), this.getValue()];
};

/**
 * Invoke onSuccess handler with next cursor value.
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.invokeNextSuccess_ = function() {

  var current_values = this.moveNext_();

  if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
    window.console.log(['onSuccess', this.current_cursor_index_].concat(current_values));
  }

  var primary_key = current_values[0];
  var index_key = current_values[1];
  var value = current_values[2];
  this.onSuccess(primary_key, index_key, value);

};


/**
 * Make cursor opening request.
 *
 * This will seek to given initial position if given. If only ini_key (primary
 * key) is given, this will rewind, if not found.
 *
 * @param {*=} ini_key primary key to resume position.
 * @param {*=} ini_index_key index key to resume position.
 * @param {boolean=} exclusive
 * @private
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.openCursor = function(ini_key, ini_index_key, exclusive) {

  /**
   * @type {ydn.db.core.req.CachedWebsqlCursor}
   */
  var me = this;
  var request;
  var sqls = ['SELECT'];
  var params = [];
  var primary_column_name = this.store_schema_.getSQLKeyColumnName();
  var q_primary_column_name = goog.string.quote(primary_column_name);
  var index = !!this.index_name ?
    this.store_schema_.getIndex(this.index_name) : null;
  var type = index ? index.getType() : this.store_schema_.getType();

  var effective_col_name = index ?
    index.getSQLIndexColumnName() : primary_column_name;
  var q_effective_col_name = goog.string.quote(effective_col_name);

  var order = ' ORDER BY ';

  if (!this.isValueCursor()) {
    if (index) {
      goog.asserts.assertString(effective_col_name);
      sqls.push(goog.string.quote(effective_col_name) + ', ' + q_primary_column_name);
      order += this.reverse ?
        goog.string.quote(effective_col_name) + ' DESC, ' +
          q_primary_column_name + ' DESC ' :
        goog.string.quote(effective_col_name) + ' ASC, ' +
          q_primary_column_name + ' ASC ';
    } else {
      sqls.push(q_primary_column_name);
      order += q_primary_column_name;
      order += this.reverse ? ' DESC' : ' ASC';
    }
  } else {
    sqls.push('*');
    if (index) {
      goog.asserts.assertString(effective_col_name);
      order += this.reverse ?
        goog.string.quote(effective_col_name) + ' DESC, ' +
          q_primary_column_name + ' DESC ' :
        goog.string.quote(effective_col_name) + ' ASC, ' +
          q_primary_column_name + ' ASC ';

    } else {
      order += q_primary_column_name;
      order += this.reverse ? ' DESC' : ' ASC';
    }

  }

  sqls.push('FROM ' + goog.string.quote(this.store_name));

  var wheres = [];
  var is_multi_entry = !!index && index.isMultiEntry();


  var key_range = this.key_range;
  if (goog.isDefAndNotNull(ini_key)) {

    if (!!this.index_name) {
      goog.asserts.assert(goog.isDefAndNotNull(ini_index_key));
      if (goog.isDefAndNotNull(this.key_range)) {
        var cmp = ydn.db.base.indexedDb.cmp(ini_index_key, this.key_range.upper);
        if (cmp == 1 || (cmp == 0 && !this.key_range.upperOpen)) {
          this.onSuccess(undefined, undefined, undefined); // out of range;
          return;
        }
        key_range = ydn.db.IDBKeyRange.bound(ini_index_key,
          this.key_range.upper, false, this.key_range.upperOpen);
      } else {
        key_range = ydn.db.IDBKeyRange.lowerBound(ini_index_key);
      }

      ydn.db.KeyRange.toSql(q_effective_col_name, type,
        key_range, wheres, params);
    } else {
      if (this.reverse) {
        key_range = ydn.db.IDBKeyRange.upperBound(ini_key, !!exclusive);
      } else {
        key_range = ydn.db.IDBKeyRange.lowerBound(ini_key, !!exclusive);
      }
      ydn.db.KeyRange.toSql(q_primary_column_name, this.store_schema_.getType(),
          key_range, wheres, params);
    }
  } else {
    if (!!this.index_name) {
      ydn.db.KeyRange.toSql(q_effective_col_name, type,
          key_range, wheres, params);
    } else {
      ydn.db.KeyRange.toSql(q_primary_column_name, this.store_schema_.getType(),
          key_range, wheres, params);
    }
  }


  if (wheres.length > 0) {
    sqls.push('WHERE ' + wheres.join(' AND '));
  }

  sqls.push(order);

//  if (this.key_only) {
//    sqls.push(' LIMIT ' + 100);
//  } else {
//    sqls.push(' LIMIT ' + 1);
//  }

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var onSuccess = function(transaction, results) {
    if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
      window.console.log([sql, results]);
    }
    me.has_pending_request = false;
    me.cursor_ = results;
    me.current_cursor_index_ = 0;
    if (!!me.index_name && goog.isDefAndNotNull(ini_key)) {
      // skip them
      var cmp = ydn.db.cmp(me.getPrimaryKey(), ini_key);
      while (cmp == -1 || (cmp == 0 && exclusive)) {
        me.current_cursor_index_++;
        cmp = ydn.db.cmp(me.getPrimaryKey(), ini_key);
      }
    }
    me.onSuccess(me.getPrimaryKey(), me.getIndexKey(), me.getValue());
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var onError = function(tr, error) {
    if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
      window.console.log([sql, tr, error]);
    }
    me.has_pending_request = false;
    me.logger.warning('get error: ' + error.message);
    me.onError(error);
    return true; // roll back

  };

  var sql = sqls.join(' ');
  var from = '{' + (!!ini_index_key ? ini_index_key + '-' : '') +
      (!!ini_key ? ini_key : '') + '}';

  me.logger.finest(this + ': opened: ' + from + ' SQL: ' +
      sql + ' : ' + ydn.json.stringify(params));
  this.tx.executeSql(sql, params, onSuccess, onError);

};


/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.hasCursor = function() {
  return !!this.cursor_ && this.current_cursor_index_ < this.cursor_.rows.length;
};


/**
 * @return {IDBKey|undefined} get index key.
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.getIndexKey = function() {

  if (this.isIndexCursor()) {
    if (this.current_cursor_index_ < this.cursor_.rows.length) {
      var row = this.cursor_.rows.item(this.current_cursor_index_);
      var index = this.store_schema_.getIndex(
        /** @type {string} */ (this.index_name));
      var type = index.getType();
      return ydn.db.schema.Index.sql2js(row[index.getSQLIndexColumnName()],
        type);
    } else {
      return undefined;
    }
  } else {
    return undefined;
  }

};


/**
 * @return {IDBKey|undefined}
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.getPrimaryKey = function() {
  if (this.current_cursor_index_ < this.cursor_.rows.length) {
    var primary_column_name = this.store_schema_.getSQLKeyColumnName();
    var row = this.cursor_.rows.item(this.current_cursor_index_);
    return ydn.db.schema.Index.sql2js(row[primary_column_name],
        this.store_schema_.getType());
  } else {
    return undefined;
  }
};


ydn.db.core.req.CachedWebsqlCursor.prototype.getEffectiveKey = function() {
  if (this.isIndexCursor()) {
    return this.getIndexKey();
  } else {
    return this.getPrimaryKey();
  }
};


/**
 * This must call only when cursor is active.
 * @return {*} return current primary key.
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.getValue = function() {
  var column_name = this.index_name ?
    this.index_name : this.store_schema_.getSQLKeyColumnName();

  if (this.current_cursor_index_ < this.cursor_.rows.length) {
    if (!this.isValueCursor()) {
      return this.getPrimaryKey();
    } else {
      var row = this.cursor_.rows.item(this.current_cursor_index_);
      return ydn.db.crud.req.WebSql.parseRow(/** @type {!Object} */ (row), this.store_schema_);

    }
  } else {
    return undefined;
  }

};


/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.clear = function() {

  if (!this.hasCursor()) {
    throw new ydn.db.InvalidAccessError();
  }

    var df = new goog.async.Deferred();
    var me = this;
    this.has_pending_request = true;

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var onSuccess = function(transaction, results) {
      if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
        window.console.log([sql, results]);
      }
      me.has_pending_request = false;
      df.callback(results.rowsAffected);
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var onError = function(tr, error) {
      if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
        window.console.log([sql, tr, error]);
      }
      me.has_pending_request = false;
      me.logger.warning('get error: ' + error.message);
      df.errback(error);
      return true; // roll back

    };

    var primary_column_name = this.store_schema_.getSQLKeyColumnName();
    var sql = 'DELETE FROM ' + this.store_schema_.getQuotedName() +
        ' WHERE ' + primary_column_name + ' = ?';
    var params = [this.getPrimaryKey()];
    me.logger.finest(this + ': clear "' + sql + '" : ' + ydn.json.stringify(params));
    this.tx.executeSql(sql, params, onSuccess, onError);
    return df;

};

/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.update = function(obj) {

  if (!this.hasCursor()) {
    throw new ydn.db.InvalidAccessError();
  }

    var df = new goog.async.Deferred();
    var me = this;
    this.has_pending_request = true;
    var primary_key = /** @type {!Array|number|string} */(this.getPrimaryKey());

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var onSuccess = function(transaction, results) {
      if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
        window.console.log([sql, results]);
      }
      me.has_pending_request = false;
      df.callback(primary_key);
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var onError = function(tr, error) {
      if (ydn.db.core.req.CachedWebsqlCursor.DEBUG) {
        window.console.log([sql, tr, error]);
      }
      me.has_pending_request = false;
      me.logger.warning('get error: ' + error.message);
      df.errback(error);
      return true; // roll back
    };

    goog.asserts.assertObject(obj);
    var out = me.store_schema_.sqlNamesValues(obj, primary_key);

    var sql = 'REPLACE INTO ' + this.store_schema_.getQuotedName() +
        ' (' + out.columns.join(', ') + ')' +
        ' VALUES (' + out.slots.join(', ') + ')' +
        ' ON CONFLICT FAIL';

    me.logger.finest(this + ': clear "' + sql + '" : ' + ydn.json.stringify(out.values));
    this.tx.executeSql(sql, out.values, onSuccess, onError);
    return df;

};


//
///**
// * Continue to next position.
// * @param {*} next_position next effective key.
// * @override
// */
//ydn.db.core.req.CachedWebsqlCursor.prototype.forward = function (next_position) {
//  //console.log(['forward', this.current_primary_key_, this.current_key_, next_position]);
//  var label = this.store_name + ':' + this.index_name;
//  if (next_position === false) {
//    // restart the iterator
//    this.logger.finest(this + ' restarting.');
//    this.openCursor();
//  } else if (this.hasCursor()) {
//    if (goog.isDefAndNotNull(next_position)) {
//      //if (goog.isArray(this.cache_keys_)) {
//      if (next_position === true) {
//        //this.cur['continue']();
//
//        this.invokeNextSuccess_();
//
//      } else {
//        //console.log('continuing to ' + next_position)
//        do {
//          var values = this.moveNext_();
//          var current_primary_key_ = values[0];
//          var current_key_ = values[1];
//          var current_value_ = values[2];
//          if (!goog.isDef(current_key_)) {
//            this.openCursor(null, next_position);
//            return;
//          }
//          if (ydn.db.cmp(current_key_, next_position) == 0) {
//            this.onSuccess(this.current_primary_key_, this.current_key_, this.current_value_);
//            return;
//          }
//        } while (goog.isDefAndNotNull(this.current_primary_key_));
//        this.openCursor(null, next_position);
//      }
////      } else {
////        if (next_position === true) {
////          this.openCursor(this.current_primary_key_, this.current_key_, true);
////        } else {
////          this.openCursor(null, next_position);
////        }
////      }
//    } else {
//      // notify that cursor iteration is finished.
//      this.onSuccess(undefined, undefined, undefined);
//      this.logger.finest(this + ' resting.');
//    }
//  } else {
//    throw new ydn.error.InvalidOperationError('Iterator:' + label + ' cursor gone.');
//  }
//};
//
//
///**
// * Continue to next primary key position.
// *
// *
// * This will continue to scan
// * until the key is over the given primary key. If next_primary_key is
// * lower than current position, this will rewind.
// * @param {*} next_primary_key
// * @param {*=} next_index_key
// * @param {boolean=} exclusive
// * @override
// */
//ydn.db.core.req.CachedWebsqlCursor.prototype.seek = function(next_primary_key,
//                                         next_index_key, exclusive) {
//
//  if (exclusive === false) {
//    // restart the iterator
//    this.logger.finest(this + ' restarting.');
//    this.openCursor(next_primary_key, next_index_key, true);
//    return;
//  }
//
//  if (!this.hasCursor()) {
//    throw new ydn.db.InternalError(this + ' cursor gone.');
//  }
//
//  if (exclusive === true &&
//      !goog.isDefAndNotNull(next_index_key) && !goog.isDefAndNotNull(next_primary_key)) {
//    this.invokeNextSuccess_();
//  } else {
//    throw new ydn.error.NotImplementedException();
//  }
//};



/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.advance = function(step) {
  if (!this.hasCursor()) {
    throw new ydn.error.InvalidOperationError(this + ' cursor gone.');
  }
  var n = this.cursor_.rows.length;
  this.current_cursor_index_ += step;
  var p_key = this.getPrimaryKey();
  var key = this.getIndexKey();
  var value = this.getValue();
  var me = this;
  setTimeout(function() {
    // we must invoke async just like IndexedDB advance, otherwise
    // run-to-completion logic will not work as expected.
    me.onSuccess(p_key, key, value);
  }, 4);

};


/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.continuePrimaryKey = function(key) {
  if (!this.hasCursor()) {
    throw new ydn.error.InvalidOperationError(this + ' cursor gone.');
  }
  var cmp = ydn.db.cmp(key, this.getPrimaryKey());
  if (cmp == 0 || (cmp == 1 && this.reverse) || (cmp == -1 && !this.reverse)) {
    throw new ydn.error.InvalidOperationError(this + ' wrong direction.');
  }
  var index_position = this.getIndexKey();
  var n = this.cursor_.rows.length;

  for (var i = 0; i < n; i++) {
    if (cmp == 0 || (cmp == 1 && this.reverse) || (cmp == -1 && !this.reverse)) {
      this.onSuccess(this.getPrimaryKey(), this.getIndexKey(), this.getValue());
      return;
    }
    this.current_cursor_index_++;
    if (index_position && index_position != this.getIndexKey()) {
      // index position must not change while continuing primary key
      this.onSuccess(this.getPrimaryKey(), this.getIndexKey(), this.getValue());
      return;
    }
    var eff_key = this.getPrimaryKey();
    cmp = goog.isDefAndNotNull(eff_key) ? ydn.db.cmp(key, eff_key) : 1;
  }
  this.onSuccess(undefined, undefined, undefined);
};


/**
 * @inheritDoc
 */
ydn.db.core.req.CachedWebsqlCursor.prototype.continueEffectiveKey = function(key) {
  if (!this.hasCursor()) {
    throw new ydn.error.InvalidOperationError(this + ' cursor gone.');
  }
  if (!goog.isDefAndNotNull(key)) {
    this.advance(1);
    return;
  }
  var cmp = ydn.db.cmp(key, this.getEffectiveKey());
  if (cmp == 0 || (cmp == 1 && this.reverse) || (cmp == -1 && !this.reverse)) {
    throw new ydn.error.InvalidOperationError(this + ' wrong direction.');
  }
  var n = this.cursor_.rows.length;

  for (var i = 0; i < n; i++) {
    if (cmp == 0 || (cmp == 1 && this.reverse) || (cmp == -1 && !this.reverse)) {
      this.onSuccess(this.getPrimaryKey(), this.getIndexKey(), this.getValue());
      return;
    }
    this.current_cursor_index_++;
    var eff_key = this.getEffectiveKey();
    cmp = goog.isDefAndNotNull(eff_key) ? ydn.db.cmp(key, eff_key) : 1;
  }
  this.onSuccess(undefined, undefined, undefined);
};



// Input 97
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Cursor.
 */


goog.provide('ydn.db.core.req.WebsqlCursor');
goog.require('ydn.db.core.req.AbstractCursor');
goog.require('ydn.db.core.req.ICursor');



/**
 * Open an index. This will resume depending on the cursor state.
 * @param {ydn.db.base.Transaction} tx tx.
 * @param {string} tx_no tx no.
 * @param {ydn.db.schema.Store} store_schema schema.
 * @param {ydn.db.base.QueryMethod=} opt_mth true for keys query method.
 * @extends {ydn.db.core.req.AbstractCursor}
 * @constructor
 * @struct
 */
ydn.db.core.req.WebsqlCursor = function(tx, tx_no, store_schema, opt_mth) {

  goog.base(this, tx, tx_no, store_schema, opt_mth);

  goog.asserts.assert(store_schema);
  /**
   * @final
   * @private
   */
  this.store_schema_ = store_schema;
  /**
   * @type {ydn.db.schema.Index}
   * @private
   */
  this.index_ = null;

  this.current_key_ = undefined;
  this.current_primary_key_ = undefined;

};
goog.inherits(ydn.db.core.req.WebsqlCursor, ydn.db.core.req.AbstractCursor);


/**
 * @define {boolean} for debugging this file.
 */
ydn.db.core.req.WebsqlCursor.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.WebsqlCursor.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.WebsqlCursor');


/**
 *
 * @type {IDBKey|undefined}
 * @private
 */
ydn.db.core.req.WebsqlCursor.prototype.current_key_;


/**
 *
 * @type {IDBKey|undefined}
 * @private
 */
ydn.db.core.req.WebsqlCursor.prototype.current_primary_key_;


/**
 *
 * @type {*}
 * @private
 */
ydn.db.core.req.WebsqlCursor.prototype.current_value_;


/**
 * @return {IDBKey|undefined}
 */
ydn.db.core.req.WebsqlCursor.prototype.getIndexKey = function() {

  return this.current_key_;

};


/**
 *  @return {IDBKey|undefined} primary key for index iterator.
 */
ydn.db.core.req.WebsqlCursor.prototype.getPrimaryKey = function() {
  return this.current_primary_key_;
};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.init = function(store_name,
    index_name, key_range, direction, is_key_cursor) {
  goog.base(this, 'init', store_name,
      index_name, key_range, direction, is_key_cursor);
  this.index_ = goog.isString(index_name) ?
      this.store_schema.getIndex(index_name) : null;
};


/**
 * @return {*}
 */
ydn.db.core.req.WebsqlCursor.prototype.getValue = function() {
  return this.current_value_;
};


/**
 * Collect result.
 * @param {Object=} opt_row a row result.
 */
ydn.db.core.req.WebsqlCursor.prototype.collect = function(opt_row) {
  this.current_key_ = undefined;
  this.current_primary_key_ = undefined;
  this.current_value_ = undefined;
  if (ydn.db.core.req.WebsqlCursor.DEBUG) {
    window.console.log(opt_row);
  }
  if (goog.isDef(opt_row)) {
    var row = opt_row;
    if (goog.isObject(row)) {
      var primary_column_name = this.store_schema.getSQLKeyColumnName();
      var primary_key = ydn.db.schema.Index.sql2js(
          row[primary_column_name], this.store_schema.getType());
      this.current_primary_key_ = primary_key;
      if (this.isIndexCursor()) {
        goog.asserts.assertString(this.index_name);
        var type = this.store_schema.getIndex(this.index_name).getType();
        this.current_key_ = ydn.db.schema.Index.sql2js(
            row[this.index_name], type);
      } else {
        this.current_key_ = primary_key;
      }
      this.current_value_ = !this.isValueCursor() ? primary_key :
          ydn.db.crud.req.WebSql.parseRow(row, this.store_schema);
    } else {
      this.current_value_ = row;
    }
  }
};


/**
 *
 * @param {?ydn.db.core.req.WebsqlCursor.callback} callback invoke.
 * @param {IDBKey} primary_key primary key.
 * @param {boolean=} opt_inclusive position is inclusive.
 * @param {number=} opt_offset offset.
 * @private
 */
ydn.db.core.req.WebsqlCursor.prototype.continuePrimaryKey_ = function(
    callback, primary_key, opt_inclusive, opt_offset) {

  // this is define only for index iterator.
  goog.asserts.assertString(this.index_name);
  // continuing primary key can only happen if there is a effective key.
  goog.asserts.assert(goog.isDefAndNotNull(this.current_key_));
  var index = this.store_schema.getIndex(this.index_name);
  var params = [];

  var index_name = this.index_name;

  var key_range = this.key_range;

  /**
   * @type {IDBKey}
   */
  var key = this.current_key_;
  if (goog.isDefAndNotNull(key_range)) {
    var lower = /** @type {IDBKey} */ (key_range.lower);
    var upper = /** @type {IDBKey} */ (key_range.upper);
    var lowerOpen = key_range.lowerOpen;
    var upperOpen = key_range.upperOpen;
    if (this.reverse) {
      upper = (goog.isDefAndNotNull(upper) &&
          ydn.db.cmp(upper, key) == -1) ?
          upper : key;
    } else {
      lower = (goog.isDefAndNotNull(lower) &&
          ydn.db.cmp(lower, key) == 1) ?
          lower : key;
    }
    if (goog.isDefAndNotNull(lower) && goog.isDefAndNotNull(upper)) {
      // here, sometime, we rely unvalid key range such as
      // lower = upper, lowerPen = true, upperOpen = false
      // IDBKeyRange will not accept such invalid key range.
      key_range = ydn.db.KeyRange.bound(lower, upper,
          !!lowerOpen, !!upperOpen);
    } else if (goog.isDefAndNotNull(lower)) {
      key_range = ydn.db.IDBKeyRange.lowerBound(lower, !!lowerOpen);
    } else {
      key_range = ydn.db.IDBKeyRange.upperBound(upper, !!upperOpen);
    }
  } else {
    if (this.reverse) {
      key_range = ydn.db.IDBKeyRange.upperBound(key);
    } else {
      key_range = ydn.db.IDBKeyRange.lowerBound(key);
    }
  }
  var e_sql = this.store_schema.inSql(params, this.query_method, index_name,
      key_range, this.reverse, this.unique);

  var p_key_range = this.reverse ?
      ydn.db.IDBKeyRange.upperBound(primary_key, !opt_inclusive) :
      ydn.db.IDBKeyRange.lowerBound(primary_key, !opt_inclusive);
  var p_sql = this.store_schema.inSql(params, this.query_method,
      this.store_schema.getSQLKeyColumnName(),
      p_key_range, this.reverse, this.unique);

  if (e_sql.where) {
    e_sql.where += ' AND ' + p_sql.where;
  } else {
    e_sql.where = p_sql.where;
  }

  var sql = 'SELECT ' + e_sql.select + ' FROM ' + e_sql.from +
      (e_sql.where ? ' WHERE ' + e_sql.where : '') +
      (e_sql.group ? ' GROUP BY ' + e_sql.group : '') +
      ' ORDER BY ' + e_sql.order;

  sql += ' LIMIT 1'; // cursor move only one step at a time.
  if (opt_offset > 0) {
    sql += ' OFFSET ' + opt_offset;
  }

  var me = this;
  /**
   * @param {SQLTransaction} tx transaction.
   * @param {SQLResultSet} results results.
   */
  var onSuccess = function(tx, results) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, results]);
    }

    if (results.rows.length > 0) {
      me.collect(results.rows.item(0));
    } else {
      me.collect();
    }
    callback.call(me, me.current_key_, me.current_primary_key_,
        me.current_value_);
    callback = null;
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var onError = function(tr, error) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, error]);
    }
    me.logger.warning('get error: ' + error.message);
    me.onError(error);
    me.collect();
    callback.call(me, me.current_primary_key_, me.current_key_,
        me.current_value_);
    callback = null;
    return false;
  };

  this.logger.finest(this + ': continuePrimary: ' + ' SQL: ' +
      sql + ' : ' + ydn.json.stringify(params));
  this.tx.executeSql(sql, params, onSuccess, onError);
};


/**
 * Continue to given effective key position.
 * @param {?ydn.db.core.req.WebsqlCursor.callback} callback invoke.
 * @param {IDBKey=} opt_key effective key.
 * @param {boolean=} opt_inclusive position is inclusive.
 * @param {number=} opt_offset offset.
 * @param {IDBKey=} opt_primary_key primary key.
 * @private
 */
ydn.db.core.req.WebsqlCursor.prototype.continueEffectiveKey_ = function(
    callback, opt_key, opt_inclusive, opt_offset, opt_primary_key) {

  var offset = opt_offset || 0;
  var open = !opt_inclusive;
  var params = [];
  var sql;

  if (this.is_index &&
      goog.isDefAndNotNull(opt_primary_key) &&
      goog.isDefAndNotNull(opt_key)) {
    sql = this.store_schema.sqlContinueIndexEffectiveKey(this.query_method,
        params, this.index_.getName(), this.key_range, opt_key,
        open, opt_primary_key, this.reverse, this.unique);
  } else if (goog.isDefAndNotNull(opt_key)) {
    var index_name = this.index_ ? this.index_.getName() : null;
    sql = this.store_schema.sqlContinueEffectiveKey(this.query_method, params,
        index_name, this.key_range, this.reverse, this.unique, opt_key, open);
  } else {
    var column = this.isPrimaryCursor() ?
        this.store_schema.getSQLKeyColumnName() :
        this.index_.getSQLIndexColumnName();
    sql = this.store_schema.toSql(params, this.query_method, column,
        this.key_range, this.reverse, this.unique);
    if (this.isIndexCursor()) {
      var order = this.reverse ? 'DESC' : 'ASC';
      sql += ', ' + this.store_schema.getSQLKeyColumnNameQuoted() + ' ASC';
    }
  }

  sql += ' LIMIT 1'; // cursor move only one step at a time.
  if (opt_offset > 0) {
    sql += ' OFFSET ' + opt_offset;
  }

  var me = this;

  /**
   * @param {SQLTransaction} tx transaction.
   * @param {SQLResultSet} results results.
   */
  var onSuccess = function(tx, results) {
    /*
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log(sql);
      for (var r = 0, rn = results.rows.length; r < rn; r++) {
        window.console.log(results.rows.item(r));
      }
    }
    */
    if (results.rows.length > 0) {
      me.collect(results.rows.item(0));
    } else {
      me.collect();
    }

    callback.call(me, me.current_key_, me.current_primary_key_,
        me.current_value_);
    callback = null;
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var onError = function(tr, error) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, tr, error]);
    }
    me.logger.warning('get error: ' + error.message);
    me.onError(error);
    me.collect();
    callback.call(me, me.current_primary_key_, me.current_key_,
        me.current_value_);
    callback = null;
    return false;

  };

  this.logger.finest(this + ': continue: ' + ' SQL: ' +
      sql + ' : ' + ydn.json.stringify(params));

  this.tx.executeSql(sql, params, onSuccess, onError);
};


/**
 * @typedef {
 *   function (
 *     this:ydn.db.core.req.AbstractCursor,
 *     (IDBKey|undefined),
 *     (IDBKey|undefined), *)
 * }
 */
ydn.db.core.req.WebsqlCursor.callback;


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.hasCursor = function() {
  return this.isActive();
};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.update = function(obj) {

  if (!this.hasCursor()) {
    throw new ydn.db.InvalidAccessError();
  }

  var df = new goog.async.Deferred();
  var me = this;
  var primary_key = /** @type {!Array|number|string} */(this.getPrimaryKey());

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var onSuccess = function(transaction, results) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, results]);
    }
    df.callback(primary_key);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var onError = function(tr, error) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, tr, error]);
    }
    df.errback(error);
    return false;
  };

  //LGBS PATCH: line commented out: goog.asserts.assertObject(obj);
  var out = me.store_schema.sqlNamesValues(obj, primary_key);

  var sql = 'REPLACE INTO ' + this.store_schema.getQuotedName() +
      ' (' + out.columns.join(', ') + ')' +
      ' VALUES (' + out.slots.join(', ') + ')';

  me.logger.finest(this + ': update "' + sql + '" : ' +
      ydn.json.stringify(out.values));
  this.tx.executeSql(sql, out.values, onSuccess, onError);
  return df;

};


/**
 * @const
 * @type {boolean}
 */
ydn.db.core.req.WebsqlCursor.MONITOR = true;


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.advance = function(step) {

  var key = this.current_key_;
  var p_key = this.current_primary_key_;
  /**
   * @param {IDBKey=} opt_key
   * @param {IDBKey=} opt_p_key
   * @param {*=} opt_value
   * @this {ydn.db.core.req.WebsqlCursor}
   */
  var on_success = function(opt_key, opt_p_key, opt_value) {
    var same_k = goog.isDefAndNotNull(key) && goog.isDefAndNotNull(opt_key) &&
        ydn.db.cmp(key, opt_key) == 0;
    if (this.isPrimaryCursor()) {
      if (same_k) {
        throw new ydn.debug.error.InternalError(
            'current: ' + key + ' next: ' + opt_key);
      }
    } else {
      var same_p_k = goog.isDefAndNotNull(opt_p_key) &&
          goog.isDefAndNotNull(p_key) &&
          ydn.db.cmp(opt_p_key, p_key) == 0;
      if (same_k && same_p_k) {
        throw new ydn.debug.error.InternalError(
            'current: ' + key + ';' + p_key + ' next: ' +
                opt_key + ';' + opt_p_key);
      }
    }
    this.onSuccess(opt_key, opt_p_key, opt_value);
  };

  goog.asserts.assert(step > 0);

  var inclusive = true;
  if (goog.isDefAndNotNull(this.current_key_) && (this.isPrimaryCursor() ||
      goog.isDefAndNotNull(this.current_primary_key_))) {
    step = step - 1;
    inclusive = false;
  }

  this.continueEffectiveKey_(
      ydn.db.core.req.WebsqlCursor.MONITOR ? on_success : this.onSuccess,
      this.current_key_, inclusive, step,
      this.current_primary_key_);

};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.continueEffectiveKey = function(key) {

  if (goog.isDefAndNotNull(key)) {
    this.continueEffectiveKey_(this.onSuccess, key, true);
  } else {
    this.advance(1);
  }

};


/**
 * Prepare key range SQL.
 * @param {!Array.<string>} params SQL params.
 * @return {ydn.db.schema.Store.SqlParts} SQL statement parts.
 */
ydn.db.core.req.WebsqlCursor.prototype.prepareBaseSql = function(params) {
  var column = this.index_ ? this.index_.getSQLIndexColumnName() :
      this.store_schema.getSQLKeyColumnName();
  var sql = this.store_schema.inSql(params, this.query_method,
      column, this.key_range, this.reverse, this.unique);
  return sql;
};


/**
 * Make cursor opening request.
 *
 *
 * @param {IDBKey=} opt_key primary key to resume position.
 * @param {IDBKey=} opt_primary_key index key to resume position.
 */
ydn.db.core.req.WebsqlCursor.prototype.openCursor = function(
    opt_key,  opt_primary_key) {

  this.continueEffectiveKey_(this.onSuccess, opt_key, false,
      0, opt_primary_key);

};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.clear = function() {

  if (!this.hasCursor()) {
    throw new ydn.db.InvalidAccessError();
  }

  var df = new goog.async.Deferred();
  var me = this;

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var onSuccess = function(transaction, results) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, results]);
    }
    df.callback(results.rowsAffected);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var onError = function(tr, error) {
    if (ydn.db.core.req.WebsqlCursor.DEBUG) {
      window.console.log([sql, tr, error]);
    }
    df.errback(error);
    return false;

  };

  var primary_column_name = this.store_schema.getSQLKeyColumnName();
  var sql = 'DELETE FROM ' + this.store_schema.getQuotedName() +
      ' WHERE ' + primary_column_name + ' = ?';
  var params = [this.getPrimaryKey()];
  me.logger.finest(this + ': clear "' + sql + '" : ' +
      ydn.json.stringify(params));
  this.tx.executeSql(sql, params, onSuccess, onError);
  return df;

};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebsqlCursor.prototype.continuePrimaryKey = function(key) {

  goog.asserts.assert(this.isIndexCursor());
  goog.asserts.assert(goog.isDefAndNotNull(this.current_key_));
  goog.asserts.assert(goog.isDefAndNotNull(this.current_primary_key_));
  // console.log(this + ' continuePrimaryKey ' + key);
  var cmp = ydn.db.cmp(key, this.current_primary_key_);
  if (cmp == 0 || (cmp == 1 && this.reverse) || (cmp == -1 && !this.reverse)) {
    throw new ydn.error.InvalidOperationError(this +
        ' to continuePrimaryKey ' +
        ' from ' + this.current_primary_key_ + ' to ' + key +
        ' on ' + this.dir + ' direction is wrong');
  }

  this.continuePrimaryKey_(this.onSuccess, key, true);

};




if (goog.DEBUG) {
  /**
   * @inheritDoc
   */
  ydn.db.core.req.WebsqlCursor.prototype.toString = function() {
    return 'WebSql' + goog.base(this, 'toString');
  };
}


// Input 98
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview WebSQL executor.
 *
 * @see http://www.w3.org/TR/webdatabase/
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.crud.req.WebSql');
goog.require('goog.async.Deferred');
goog.require('goog.debug.Logger');
goog.require('ydn.db.Where');
goog.require('ydn.db.crud.req.IRequestExecutor');
goog.require('ydn.db.crud.req.RequestExecutor');
goog.require('ydn.json');



/**
 * @extends {ydn.db.crud.req.RequestExecutor}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.crud.req.IRequestExecutor}
 * @struct
 */
ydn.db.crud.req.WebSql = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.crud.req.WebSql, ydn.db.crud.req.RequestExecutor);


/**
 * @const
 * @type {boolean} debug flag.
 */
ydn.db.crud.req.WebSql.DEBUG = false;


/**
 * Maximum number of readonly requests created per transaction.
 * Common implementation in WebSQL library is sending massive requests
 * to the transaction and use setTimeout to prevent breaking the system.
 * To get optimal performance, we send limited number of request per
 * transaction.
 * Sending more request will not help much because JS is just parsing and
 * pushing to result array data which is faster than SQL processing.
 * Smaller number also help SQLite engine to give
 * other transaction to perform parallel requests.
 * @const
 * @type {number} Maximum number of readonly requests created per transaction.
 */
ydn.db.crud.req.WebSql.REQ_PER_TX = 10;


/**
 * Maximum number of read-write requests created per transaction.
 * Since SQLite locks all stores during read write request, it is better
 * to give this number smaller. Larger number will not help to get faster
 * because it bottleneck is in SQL engine, not from JS side.
 * @const
 * @type {number} Maximum number of read-write requests created per transaction.
 */
ydn.db.crud.req.WebSql.RW_REQ_PER_TX = 2;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.crud.req.WebSql.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.crud.req.WebSql');


/**
 * Parse resulting object of a row into original object as it 'put' into the
 * database.
 * @final
 * @param {!Object} row row.
 * @param {ydn.db.schema.Store} store store schema.
 * @return {!Object} parse value.
 */
ydn.db.crud.req.WebSql.parseRow = function(row, store) {

  if (store.isFixed() && !store.usedInlineKey() && store.countIndex() == 0 &&
      row[ydn.db.base.DEFAULT_BLOB_COLUMN]) {
    // check for blob or file
    var s = row[ydn.db.base.DEFAULT_BLOB_COLUMN];
    var BASE64_MARKER = ';base64,';
    if (s.indexOf(BASE64_MARKER) == -1) {
      return ydn.json.parse(s);
    } else {
      if (s.charAt(0) == '"' && s.charAt(s.length - 1) == '"') {
        s = s.substr(1, s.length - 2);
      }
      var parts = s.split(BASE64_MARKER);
      var contentType = parts[0].split(':')[1];
      var raw = window.atob(parts[1]);
      var rawLength = raw.length;

      var uInt8Array = new Uint8Array(rawLength);

      for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
      }

      return new Blob([uInt8Array.buffer], {type: contentType});
    }
  }
  var value = row[ydn.db.base.DEFAULT_BLOB_COLUMN] ?
      ydn.json.parse(row[ydn.db.base.DEFAULT_BLOB_COLUMN]) : {};
  if (goog.isDefAndNotNull(store.keyPath)) {
    var key = ydn.db.schema.Index.sql2js(row[store.keyPath], store.getType());
    if (goog.isDefAndNotNull(key)) {
      store.setKeyValue(value, key);
    }
  }

  for (var j = 0; j < store.countIndex(); j++) {
    var index = store.index(j);
    var column_name = index.getSQLIndexColumnName();
    if (column_name == ydn.db.base.DEFAULT_BLOB_COLUMN ||
        index.isComposite() || index.isMultiEntry()) {
      continue;
    }
    if (index.getType() == ydn.db.schema.DataType.DATE ||
        store.isFixed()) { // fixed schema does not stored data in default blob
      // in JSON serialization, date lost type.
      var x = row[column_name];
      var v = ydn.db.schema.Index.sql2js(x, index.getType());
      if (goog.isDef(v)) {
        index.applyValue(value, v);
      }
    }
  }

  return value;
};


/**
 * Extract key from row result.
 * @final
 * @protected
 * @param {ydn.db.schema.Store} table table of concern.
 * @param {!Object} row row.
 * @return {!Object} parse value.
 */
ydn.db.crud.req.WebSql.prototype.getKeyFromRow = function(table, row) {
  return row[table.keyPath || ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME];
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.putByKeys = goog.abstractMethod;


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.putData = function(tx, tx_no, df,
    store_name, data, delimiter) {
  throw new ydn.debug.error.NotImplementedException('putData');
};


/**
 * @param {ydn.db.Request} req tx.
 * @param {boolean} is_replace true if `put`, otherwise `add`.
 * @param {boolean} single false for array input.
 * @param {string} store_name table name.
 * @param {!Array.<!Object>} objects object to put.
 * @param {!Array.<(!Array|string|number)>=} opt_keys optional out-of-line keys.
 * @protected
*/
ydn.db.crud.req.WebSql.prototype.insertObjects = function(
    req, is_replace, single, store_name, objects, opt_keys) {
  var create = !is_replace;
  var table = this.schema.getStore(store_name);

  var insert_statement = create ? 'INSERT INTO ' : 'INSERT OR REPLACE INTO ';

  var tx = req.getTx();
  var me = this;
  var result_keys = [];
  var result_count = 0;
  var msg = req.getLabel() + ' inserting ' + objects.length + ' objects.';
  var has_error = false;

  /**
   * Put and item at i. This ydn.db.con.Storage will invoke callback to df if
   * all objects
   * have been put, otherwise recursive call to itself at next i+1 item.
   * @param {number} i index.
   * @param {SQLTransaction} tx transaction.
   */
  var put = function(i, tx) {

    if (!goog.isDefAndNotNull(objects[i])) {
      me.logger.finest('empty object at ' + i + ' of ' + objects.length);
      result_count++;
      if (result_count == objects.length) {
        me.logger.finer(msg + ' success ' + msg);
        // console.log(msg, result_keys);
        req.setDbValue(result_keys, has_error);
      } else {
        var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX;
        if (next < objects.length) {
          put(next, tx);
        }
      }
    }

    var out;
    if (goog.isDef(opt_keys)) {
      out = table.sqlNamesValues(objects[i], opt_keys[i]);
    } else {
      out = table.sqlNamesValues(objects[i]);
    }

    //console.log([obj, JSON.stringify(obj)]);

    var sql = insert_statement + table.getQuotedName() +
        ' (' + out.columns.join(', ') + ') ' +
        'VALUES (' + out.slots.join(', ') + ');';

    var i_msg = req.getLabel() +
        ' SQL: ' + sql + ' PARAMS: ' + out.values +
        ' REQ: ' + i + ' of ' + objects.length;

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var success_callback = function(transaction, results) {
      result_count++;

      var key = goog.isDef(out.key) ? out.key : results.insertId;
      if (results.rowsAffected < 1) { // catch for no-op
        // assuming index constraint no op
        has_error = true;
        key = new ydn.db.ConstraintError(key + ' no-op');
      }

      /**
       * Insert a row for each multi entry index.
       * @param {ydn.db.schema.Index} index multi entry index.
       * @param {number} value index at.
       */
      var insertMultiEntryIndex = function(index, value) {
        var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
            table.getName() + ':' + index.getName();
        var idx_sql = insert_statement + goog.string.quote(idx_name) + ' (' +
            table.getSQLKeyColumnNameQuoted() + ', ' +
            index.getSQLIndexColumnNameQuoted() + ') VALUES (?, ?)';
        var idx_params = [ydn.db.schema.Index.js2sql(key, table.getType()),
              ydn.db.schema.Index.js2sql(value, index.getType())];

        /**
         * @param {SQLTransaction} tx transaction.
         * @param {SQLResultSet} rs results.
         */
        var idx_success = function(tx, rs) {

        };
        /**
         * @param {SQLTransaction} tr transaction.
         * @param {SQLError} error error.
         * @return {boolean} true to roll back.
         */
        var idx_error = function(tr, error) {
          me.logger.warning('multiEntry index insert error: ' + error.message);
          return false;
        };

        me.logger.finest(req.getLabel() + ' multiEntry ' + idx_sql +
            ' ' + idx_params);
        tx.executeSql(idx_sql, idx_params, idx_success, idx_error);
      };
      for (var j = 0, nj = table.countIndex(); j < nj; j++) {
        var idx = table.index(j);
        if (idx.isMultiEntry()) {
          var index_values = ydn.db.utils.getValueByKeys(objects[i],
              idx.getKeyPath());
          var n = (!index_values ? 0 : index_values.length) || 0;
          for (var k = 0; k < n; k++) {
            insertMultiEntryIndex(idx, index_values[k]);
          }
        }
      }

      if (single) {
        // console.log(msg, key);
        req.setDbValue(key);
      } else {
        result_keys[i] = key;
        if (result_count == objects.length) {
          // console.log(msg, result_keys);
          req.setDbValue(result_keys, has_error);
        } else {
          var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX;
          if (next < objects.length) {
            put(next, transaction);
          }
        }
      }
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var error_callback = function(tr, error) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([sql, out, tr, error]);
      }
      result_count++;
      has_error = true;
      if (error.code == 6) { // constraint failed
        error.name = 'ConstraintError';
      } else {
        me.logger.warning('error: ' + error.message + ' ' + msg);
      }
      if (single) {
        req.setDbValue(error, true);
      } else {
        result_keys[i] = error;
        if (result_count == objects.length) {
          me.logger.finest('success ' + msg); // still success message ?
          req.setDbValue(result_keys, has_error);
        } else {
          var next = i + ydn.db.crud.req.WebSql.RW_REQ_PER_TX;
          if (next < objects.length) {
            put(next, tr);
          }
        }
      }
      return false; // continue, not rollback
    };

    // console.log([sql, out.values]);
    me.logger.finest(i_msg);
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log(sql, out.values);
    }
    tx.executeSql(sql, out.values, success_callback, error_callback);
  };

  if (objects.length > 0) {
    // send parallel requests
    for (var i = 0;
         i < ydn.db.crud.req.WebSql.RW_REQ_PER_TX && i < objects.length; i++) {
      put(i, /** @type {SQLTransaction} */ (tx));
    }
  } else {
    this.logger.finer('success');
    req.setDbValue([]);
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.putByKeys = function(rq, objs, keys) {

  if (keys.length == 0) {
    rq.setDbValue([]);
    return;
  }

  var tx = rq.getTx();
  var results = [];
  var count = 0;
  var total = 0;
  var me = this;

  /**
   *
   * @param {string} store_name
   * @param {!Array.<number>} idx
   */
  var execute_on_store = function(store_name, idx) {
    var idx_objs = [];
    me.logger.finest('put ' + idx.length + ' objects to ' + store_name);
    var store = me.schema.getStore(store_name);
    var inline = store.usedInlineKey();
    var idx_keys = inline ? undefined : [];
    for (var i = 0; i < idx.length; i++) {
      idx_objs.push(objs[idx[i]]);
      if (!inline) {
        idx_keys.push(keys[idx[i]].getId());
      }
    }
    var i_rq = rq.copy();
    i_rq.addCallbacks(function(xs) {
      for (var i = 0; i < idx.length; i++) {
        results[idx[i]] = xs[i];
      }
      count++;
      if (count == total) {
        rq.setDbValue(results);
      }
    }, function(e) {
      count++;
      if (count == total) {
        rq.setDbValue(results, true);
      }
    });
    me.insertObjects(i_rq, false, false, store_name, idx_objs,
        idx_keys);

  };

  var store_name = '';
  var store;
  var idx = [];
  var ids = [];
  for (var i = 0; i < keys.length; i++) {
    var name = keys[i].getStoreName();
    var id = keys[i].getId();

    if (name != store_name) {
      total++;
      if (idx.length > 0) {
        execute_on_store(store_name, idx);
      }
      idx = [i];
      ids = [id];
      store_name = name;
    } else {
      idx.push(i);
      ids.push(id);
    }

  }

  if (idx.length > 0) {
    execute_on_store(store_name, idx);
  }

};


/**
*
* @inheritDoc
*/
ydn.db.crud.req.WebSql.prototype.getById = function(req, table_name, id) {

  var tx = req.getTx();
  var table = this.schema.getStore(table_name);
  goog.asserts.assertInstanceof(table, ydn.db.schema.Store, table_name +
      ' not found.');

  var me = this;

  var column_name = table.getSQLKeyColumnNameQuoted();

  var params = [ydn.db.schema.Index.js2sql(id, table.getType())];

  var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' +
      column_name + ' = ?';

  var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params;

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {

    if (results.rows.length > 0) {
      var row = results.rows.item(0);

      if (goog.isDefAndNotNull(row)) {
        var value = ydn.db.crud.req.WebSql.parseRow(row, table);
        req.setDbValue(value);
      } else {
        me.logger.finer('success no result: ' + msg);
        req.setDbValue(undefined);
      }
    } else {
      me.logger.finer('success no result: ' + msg);
      req.setDbValue(undefined);
    }
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([tr, error]);
    }
    me.logger.warning('error: ' + msg + ' ' + error.message);
    req.setDbValue(error, true);
    return false;
  };

  //window.console.log(['getById', sql, params]);
  this.logger.finest(msg);
  tx.executeSql(sql, params, callback, error_callback);
};


/**
 *
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.listByIds = function(req, table_name, ids) {

  var tx = req.getTx();
  var me = this;
  var objects = [];
  var result_count = 0;

  var table = this.schema.getStore(table_name);

  /**
   * Get fetch the given id of i position and put to results array in
   * i position. If req_done are all true, df will be invoked, if not
   * it recursively call itself to next sequence.
   * @param {number} i the index of ids.
   * @param {SQLTransaction} tx tx.
   */
  var get = function(i, tx) {

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var callback = function(transaction, results) {
      result_count++;
      if (results.rows.length > 0) {
        var row = results.rows.item(0);
        if (goog.isDefAndNotNull(row)) {
          objects[i] = ydn.db.crud.req.WebSql.parseRow(row, table);
        }
        // this is get function, we take only one result.
      } else {
        objects[i] = undefined; // not necessary.
      }

      if (result_count == ids.length) {
        req.setDbValue(objects);
      } else {
        var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX;
        if (next < ids.length) {
          get(next, transaction);
        }
      }
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var error_callback = function(tr, error) {
      result_count++;
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      me.logger.warning('error: ' + sql + ' ' + error.message);
      // t.abort(); there is no abort
      if (result_count == ids.length) {
        req.setDbValue(objects);
      } else {
        var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX;
        if (next < ids.length) {
          get(next, tr);
        }
      }
      return false;
    };

    var id = ids[i];
    var column_name = table.getSQLKeyColumnNameQuoted();

    var params = [ydn.db.schema.Index.js2sql(id, table.getType())];
    var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' +
        column_name + ' = ?';
    me.logger.finest('SQL: ' + sql + ' PARAMS: ' + params);
    tx.executeSql(sql, params, callback, error_callback);
  };

  if (ids.length > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.WebSql.REQ_PER_TX && i < ids.length;
         i++) {
      get(i, /** @type {SQLTransaction} */ (tx));
    }
  } else {
    me.logger.finer('success');
    req.setDbValue([]);
  }
};


/**
*
* @inheritDoc
*/
ydn.db.crud.req.WebSql.prototype.listByKeys = function(req, keys) {

  var tx = req.getTx();
  var me = this;
  var objects = [];
  var result_count = 0;

  var get = function(i, tx) {
    var key = keys[i];
    var table_name = key.getStoreName();
    var table = me.schema.getStore(table_name);

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var callback = function(transaction, results) {
      result_count++;
      if (results.rows.length > 0) {
        var row = results.rows.item(0);
        if (goog.isDefAndNotNull(row)) {
          objects[i] = ydn.db.crud.req.WebSql.parseRow(row, table);
        }
        // this is get function, we take only one result.
      } else {
        objects[i] = undefined; // not necessary.
      }

      if (result_count == keys.length) {
        me.logger.finest('success ' + sql);
        req.setDbValue(objects);
      } else {
        var next = i + ydn.db.crud.req.WebSql.REQ_PER_TX;
        if (next < keys.length) {
          get(next, transaction);
        }
      }

    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var error_callback = function(tr, error) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      req.setDbValue(error, true);
      return false;
    };

    var id = key.getNormalizedId();
    var column_name = table.getSQLKeyColumnNameQuoted();

    var params = [ydn.db.schema.Index.js2sql(id, table.getType())];
    var sql = 'SELECT * FROM ' + table.getQuotedName() + ' WHERE ' +
        column_name + ' = ?';
    me.logger.finest('SQL: ' + sql + ' PARAMS: ' + params);
    tx.executeSql(sql, params, callback, error_callback);

  };

  if (keys.length > 0) {
    // send parallel requests
    for (var i = 0; i < ydn.db.crud.req.WebSql.REQ_PER_TX && i < keys.length;
         i++) {
      get(i, tx);
    }
  } else {
    this.logger.finest('success');
    req.setDbValue([]);
  }
};


/**
* @inheritDoc
*/
ydn.db.crud.req.WebSql.prototype.clearByStores = function(req, store_names) {

  var tx = req.getTx();
  var me = this;

  var deleteStore = function(i, tx) {

    var store = me.schema.getStore(store_names[i]);

    var sql = 'DELETE FROM  ' + store.getQuotedName();

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var callback = function(transaction, results) {
      if (i == store_names.length - 1) {
        me.logger.finest('success ' + sql);
        req.setDbValue(store_names.length);
      } else {
        deleteStore(i + 1, transaction);
      }
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var errback = function(tr, error) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      req.setDbValue(error, true);
      return false;
    };

    me.logger.finest('SQL: ' + sql + ' PARAMS: []');
    tx.executeSql(sql, [], callback, errback);

    /**
     *
     * @param {ydn.db.schema.Index} index
     */
    var deleteMultiEntryIndex = function(index) {
      var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
          store.getName() + ':' + index.getName();

      var idx_sql = 'DELETE FROM  ' + goog.string.quote(idx_name);
      me.logger.finest('SQL: ' + idx_sql);
      tx.executeSql(idx_sql, []);
    };

    for (var j = 0, n = store.countIndex(); j < n; j++) {
      var index = store.index(j);
      if (index.isMultiEntry()) {
        deleteMultiEntryIndex(index);
      }
    }

  };

  if (store_names.length > 0) {
    deleteStore(0, tx);
  } else {
    this.logger.finest('success');
    req.setDbValue(0);
  }
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.removeByKeys = function(req, keys) {

  var tx = req.getTx();
  var me = this;
  var count = 0;
  var has_failed = false;
  var store_name, store, key;
  var msg = req.getLabel() + ' removeByKeys: ' + keys.length + ' keys';
  this.logger.finest(msg);

  var removeAt = function(i) {

    if (i >= keys.length) {
      req.setDbValue(count, has_failed);
      return;
    }

    var store = me.schema.getStore(keys[i].getStoreName());

    var key = ydn.db.schema.Index.js2sql(keys[i].getId(), store.getType());

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var success_callback = function(transaction, results) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log(results);
      }
      count++;
      removeAt(i);
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var error_callback = function(tr, error) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      me.logger.warning('error: ' + i_msg + error.message);
      has_failed = true;
      removeAt(i);
      return false;
    };

    var where = ' WHERE ' + store.getSQLKeyColumnNameQuoted() + ' = ?';
    var sql = 'DELETE FROM ' + store.getQuotedName() + where;
    //console.log([sql, out.values])
    var i_msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + [key];
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log(i_msg);
    }
    tx.executeSql(sql, [key], success_callback, error_callback);
    i++;

    /**
     *
     * @param {ydn.db.schema.Index} index
     */
    var deleteMultiEntryIndex = function(index) {
      var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
          store.getName() + ':' + index.getName();

      var idx_sql = 'DELETE FROM  ' + goog.string.quote(idx_name) + where;
      me.logger.finest(req.getLabel() + + ' SQL: ' + idx_sql);
      tx.executeSql(idx_sql, [key]);
    };

    for (var j = 0, n = store.countIndex(); j < n; j++) {
      var index = store.index(j);
      if (index.isMultiEntry()) {
        deleteMultiEntryIndex(index);
      }
    }
  };

  removeAt(0);

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.removeById = function(req, table, id) {

  var tx = req.getTx();
  var store = this.schema.getStore(table);
  var key = ydn.db.schema.Index.js2sql(id, store.getType());

  var me = this;

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var success_callback = function(transaction, results) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log(results);
    }
    req.setDbValue(results.rowsAffected);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([tr, error]);
    }
    req.setDbValue(error, true);
    return false; // not rollback yet.
  };

  var where = ' WHERE ' + store.getSQLKeyColumnNameQuoted() + ' = ?';
  var sql = 'DELETE FROM ' + store.getQuotedName() + where;
  //console.log([sql, out.values])
  var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + [key];
  this.logger.finest(msg);
  tx.executeSql(sql, [key], success_callback, error_callback);

  /**
   *
   * @param {ydn.db.schema.Index} index
   */
  var deleteMultiEntryIndex = function(index) {
    var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
        store.getName() + ':' + index.getName();

    var idx_sql = 'DELETE FROM  ' + goog.string.quote(idx_name) + where;
    me.logger.finest(req.getLabel() + + ' SQL: ' + idx_sql);
    tx.executeSql(idx_sql, [key]);
  };

  for (var j = 0, n = store.countIndex(); j < n; j++) {
    var index = store.index(j);
    if (index.isMultiEntry()) {
      deleteMultiEntryIndex(index);
    }
  }

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.clearByKeyRange = function(req,
    store_name, key_range) {
  this.clear_by_key_range_(req, store_name, undefined, key_range);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.removeByKeyRange = function(req,
    store_name, key_range) {
  this.clear_by_key_range_(req, store_name, undefined, key_range);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.removeByIndexKeyRange = function(req,
    store_name, index_name, key_range) {
  this.clear_by_key_range_(req, store_name, index_name, key_range);
};


/**
 * Retrieve primary keys or value from a store in a given key range.
 * @param {ydn.db.Request} req request.
 * @param {string} store_name table name.
 * @param {string|undefined} column_name name.
 * @param {IDBKeyRange} key_range to retrieve.
 * @private
 */
ydn.db.crud.req.WebSql.prototype.clear_by_key_range_ = function(req,
    store_name, column_name, key_range) {

  var tx = req.getTx();
  var me = this;
  var arr = [];
  var store = this.schema.getStore(store_name);

  var sql = 'DELETE FROM ' + store.getQuotedName();
  var params = [];
  var where_params = [];
  var where = '';
  if (goog.isDefAndNotNull(key_range)) {
    if (goog.isDef(column_name)) {
      var index = store.getIndex(column_name);
      ydn.db.KeyRange.toSql(index.getSQLIndexColumnNameQuoted(),
          index.getType(), key_range, where_params, params);
    } else {
      ydn.db.KeyRange.toSql(store.getSQLKeyColumnNameQuoted(), store.getType(),
          key_range, where_params, params);
    }
    where = ' WHERE ' + where_params.join(' AND ');
  }
  sql += where;

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {
    me.logger.finest('success ' + msg);
    req.setDbValue(results.rowsAffected);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([tr, error]);
    }
    me.logger.warning('error: ' + msg + error.message);
    req.setDbValue(error, true);
    return false;
  };

  //console.log([sql, params])
  var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params;
  this.logger.finest(msg);
  tx.executeSql(sql, params, callback, error_callback);

  /**
   *
   * @param {ydn.db.schema.Index} index
   */
  var deleteMultiEntryIndex = function(index) {
    var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
        store.getName() + ':' + index.getName();

    var idx_sql = 'DELETE FROM  ' + goog.string.quote(idx_name) + where;
    me.logger.finest(req.getLabel() + + ' SQL: ' + idx_sql);
    tx.executeSql(idx_sql, where_params);
  };

  for (var j = 0, n = store.countIndex(); j < n; j++) {
    var j_index = store.index(j);
    if (j_index.isMultiEntry()) {
      deleteMultiEntryIndex(j_index);
    }
  }
};


/**
 * @inheritDoc
*/
ydn.db.crud.req.WebSql.prototype.countStores = function(req, tables) {

  var tx = req.getTx();
  var me = this;
  var out = [];

  /**
   *
   * @param {number} i
   */
  var count = function(i) {
    var table = tables[i];
    var sql = 'SELECT COUNT(*) FROM ' + goog.string.quote(table);

    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var callback = function(transaction, results) {
      var row = results.rows.item(0);
      // console.log(['row ', row  , results]);
      out[i] = parseInt(row['COUNT(*)'], 10);
      i++;
      if (i == tables.length) {
        req.setDbValue(out);
      } else {
        count(i);
      }

    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     * @return {boolean} true to roll back.
     */
    var error_callback = function(tr, error) {
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      req.setDbValue(error, true);
      return false;
    };

    me.logger.finest('SQL: ' + sql + ' PARAMS: []');
    tx.executeSql(sql, [], callback, error_callback);
  };

  if (tables.length == 0) {
    this.logger.finest('success');
    req.setDbValue(0);
  } else {
    count(0);
  }

};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.countKeyRange = function(req, table,
    key_range, index_name, unique) {

  var me = this;

  var params = [];

  var store = this.schema.getStore(table);

  var sql = store.toSql(params, ydn.db.base.QueryMethod.COUNT,
      index_name, key_range, false, unique);

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([sql, results]);
    }
    var row = results.rows.item(0);
    // console.log(['row ', row  , results]);
    req.setDbValue(ydn.object.takeFirst(row)); // usually row['COUNT(*)']
    // , but may be  row['COUNT("id")']
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([sql, error]);
    }
    req.setDbValue(error, true);
    return false;
  };

  var msg = req.getLabel() + ' SQL: ' + sql + ' PARAMS: ' + params;
  this.logger.finest(msg);
  req.getTx().executeSql(sql, params, callback, error_callback);
};


/**
 * @inheritDoc
 */
ydn.db.crud.req.WebSql.prototype.list = function(req, mth, store_name,
    index_column, key_range, limit, offset, reverse, distinct, opt_position) {

  var me = this;
  var arr = [];
  var store = this.schema.getStore(store_name);
  var key_column = store.getSQLKeyColumnName();
  var primary_type = store.getType();
  var effective_type = primary_type;
  var index = goog.isDefAndNotNull(index_column) &&
      (index_column !== key_column) ? store.getIndex(index_column) : null;
  var effective_column = index_column || key_column;
  if (index) {
    effective_type = index.getType();
  }
  var params = [];
  var sql;
  if (!!opt_position && goog.isDef(opt_position[0])) {
    var e_key = /** @type {IDBKey} */ (opt_position[0]);
    if (index && goog.isDef(opt_position[1])) {
      var p_key = /** @type {IDBKey} */ (opt_position[1]);
      sql = store.sqlContinueIndexEffectiveKey(mth, params, index.getName(),
          key_range, e_key, true, p_key, reverse, distinct);
    } else {
      sql = store.sqlContinueEffectiveKey(mth, params, index_column,
          key_range, reverse, distinct, e_key, true);
    }
  } else {
    sql = store.toSql(params, mth, effective_column,
        key_range, reverse, distinct);
  }

  if (limit !== Number.MAX_VALUE || offset !== 0) { //LGBS PATCH: added "IF" clause
      if (goog.isNumber(limit)) {
          sql += ' LIMIT ' + limit;
      }
      if (goog.isNumber(offset)) {
          sql += ' OFFSET ' + offset;
      }
  }

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {
    var n = results.rows.length;
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log(results);
    }
    var row;
    for (var i = 0; i < n; i++) {
      row = results.rows.item(i);
      if (ydn.db.crud.req.WebSql.DEBUG) {
        window.console.log(row);
      }
      if (mth == ydn.db.base.QueryMethod.LIST_PRIMARY_KEY) {
        arr[i] = ydn.db.schema.Index.sql2js(row[key_column], primary_type);
      } else if (mth == ydn.db.base.QueryMethod.LIST_KEY) {
        arr[i] = ydn.db.schema.Index.sql2js(row[effective_column],
            effective_type);
      } else if (mth == ydn.db.base.QueryMethod.LIST_KEYS) {
        arr[i] = [
          ydn.db.schema.Index.sql2js(row[effective_column], effective_type),
          ydn.db.schema.Index.sql2js(row[key_column], primary_type)];
      } else if (goog.isDefAndNotNull(row)) {
        // LIST_VALUE
        arr[i] = ydn.db.crud.req.WebSql.parseRow(row, store);
      }
    }
    me.logger.finer('success ' + req);
    if (opt_position && row) {
      opt_position[0] = ydn.db.schema.Index.sql2js(row[effective_column],
          effective_type);
      opt_position[1] = ydn.db.schema.Index.sql2js(row[key_column],
          primary_type);
    }
    req.setDbValue(arr);
  };

  var msg = req + ' SQL: ' + sql + ' ;params= ' +
      ydn.json.stringify(params);

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.crud.req.WebSql.DEBUG) {
      window.console.log([tr, error]);
    }
    me.logger.warning('error: ' + msg + error.message);
    req.setDbValue(error, true);
    return false;
  };

  this.logger.finest(msg);
  req.getTx().executeSql(sql, params, callback, error_callback);
};

// Input 99
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview WebSQL executor.
 *
 * @see http://www.w3.org/TR/webdatabase/
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.core.req.WebSql');
goog.require('goog.async.Deferred');
goog.require('goog.debug.Logger');
goog.require('ydn.db.core.req.CachedWebsqlCursor');
goog.require('ydn.db.core.req.IRequestExecutor');
goog.require('ydn.db.core.req.WebsqlCursor');
goog.require('ydn.db.crud.req.WebSql');
goog.require('ydn.json');



/**
 * @extends {ydn.db.crud.req.WebSql}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.core.req.IRequestExecutor}
 */
ydn.db.core.req.WebSql = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.core.req.WebSql, ydn.db.crud.req.WebSql);


/**
 * @const
 * @type {boolean} debug flag.
 */
ydn.db.core.req.WebSql.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.core.req.WebSql.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.core.req.WebSql');


/**
 * @inheritDoc
 */
ydn.db.core.req.WebSql.prototype.getCursor = function(tx, tx_no, store_name,
                                                      mth) {

  var store = this.schema.getStore(store_name);
  goog.asserts.assertObject(store, 'store "' + store_name + '" not found.');
  return new ydn.db.core.req.WebsqlCursor(tx, tx_no, store, mth);
};


/**
 * @inheritDoc
 */
ydn.db.core.req.WebSql.prototype.getStreamer = function(tx, tx_no,
    store_name, index_name) {
  throw 'not yet';
};



// Input 100
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Inject request executors.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.core.Storage.inject_executor');
goog.require('ydn.db.core.Storage');
goog.require('ydn.db.core.req.IndexedDb');
goog.require('ydn.db.core.req.SimpleStore');
goog.require('ydn.db.core.req.WebSql');


/**
 * @return {!ydn.db.core.req.IRequestExecutor}
 */
ydn.db.core.Storage.prototype.newExecutor = function() {
  var type = this.getType();
  if (type == ydn.db.base.Mechanisms.IDB) {
    return new ydn.db.core.req.IndexedDb(this.db_name, this.schema);
  } else if (type == ydn.db.base.Mechanisms.WEBSQL) {
    return new ydn.db.core.req.WebSql(this.db_name, this.schema);
  } else if (type == ydn.db.base.Mechanisms.MEMORY_STORAGE ||
      type == ydn.db.base.Mechanisms.LOCAL_STORAGE ||
      type == ydn.db.base.Mechanisms.USER_DATA ||
      type == ydn.db.base.Mechanisms.SESSION_STORAGE) {
    return new ydn.db.core.req.SimpleStore(this.db_name, this.schema);
  } else {
    throw new ydn.debug.error.InternalError('No executor for ' + type);
  }
};


// Input 101
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query object to feed WebSQL iterator.
 *
 *
 */


goog.provide('ydn.db.sql.req.IterableQuery');
goog.require('ydn.db.Iterator');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.error.ArgumentException');



/**
 * Create a SQL query object from a query object.
 *
 * This clone given query object and added iteration functions so that
 * query processor can mutation as part of query optimization processes.
 *
 * @param {string} store store name.
 * @param {string=} index store field, where key query is preformed. If not
 * provided, the first index will be used.
 * @param {(ydn.db.KeyRange|ydn.db.IDBKeyRange)=} keyRange configuration in json or native format.
 * @param {boolean=} reverse reverse.
 * @param {boolean=} unique unique.
 * @param {boolean=} key_only true for key only iterator.
 * @param {Function=} filter filter function.
 * @param {Function=} continued continued function.
 * @extends {ydn.db.Iterator}
 * @constructor
 */
ydn.db.sql.req.IterableQuery = function(store, index, keyRange, reverse,
      unique, key_only, filter, continued) {

  goog.base(this, store, index, keyRange, reverse, unique, key_only);

  // set all null so that no surprise from inherit prototype
  this.initial = null;
  this.map = null;
  this.reduce = null;
  this.finalize = null;

  this.filter_fn = filter || null;
  this.continued = continued || null;

};
goog.inherits(ydn.db.sql.req.IterableQuery, ydn.db.Iterator);



/**
 * @inheritDoc
 */
ydn.db.sql.req.IterableQuery.prototype.toJSON = function() {
  var obj = goog.base(this, 'toJSON');
  obj['initial'] = this.initial ? this.initial.toString() : null;
  obj['map'] = this.map ? this.map.toString() : null;
  obj['reduce'] = this.reduce ? this.reduce.toString() : null;
  obj['finalize'] = this.finalize ? this.finalize.toString() : null;
  return obj;
};


/**
 * @type {?function(): *}
 */
ydn.db.sql.req.IterableQuery.prototype.initial = null;


/**
 * @type {?function(*): *}
 */
ydn.db.sql.req.IterableQuery.prototype.map = null;

/**
 * Reduce is execute after map.
 * @type {?function(*, *, number): *}
 * function(previousValue, currentValue, index)
 */
ydn.db.sql.req.IterableQuery.prototype.reduce = null;


/**
 * @type {?function(*): *}
 */
ydn.db.sql.req.IterableQuery.prototype.finalize = null;


/**
 * @override
 */
ydn.db.sql.req.IterableQuery.prototype.toString = function() {
  var idx = goog.isDef(this.getIndexName()) ? ':' + this.getIndexName() : '';
  return 'Cursor:' + this.getStoreName() + idx;
};


/**
 * Process where instruction into filter iteration method.
 * @param {!ydn.db.Where} where where.
 */
ydn.db.sql.req.IterableQuery.prototype.processWhereAsFilter = function(where) {

  var prev_filter = goog.functions.TRUE;
  if (goog.isFunction(this.filter_fn)) {
    prev_filter = this.filter_fn;
  }

  this.filter_fn = function(obj) {
    var value = obj[where.getField()];
    var ok1 = true;
    var key_range = where.getKeyRange();
    if (key_range) {
      if (goog.isDefAndNotNull(key_range.lower)) {
        ok1 = key_range.lowerOpen ? value < key_range.lower :
          value <= key_range.lower;
      }
      var ok2 = true;
      if (goog.isDefAndNotNull(key_range.upper)) {
        ok2 = key_range.upperOpen ? value > key_range.upper :
          value >= key_range.upper;
      }
    }

    return prev_filter(obj) && ok1 && ok2;
  };

  //console.log([where, this.filter.toString()]);

};





// Input 102
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query object to feed WebSQL iterator.
 *
 *
 */


goog.provide('ydn.db.sql.req.IdbQuery');
goog.require('ydn.db.sql.req.IterableQuery');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.error.ArgumentException');



/**
 * Create a SQL query object from a query object.
 *
 * This clone given query object and added iteration functions so that
 * query processor can mutation as part of query optimization processes.
 *
 * @param {string} store store name.
 * @param {string=} index store field, where key query is preformed. If not
 * provided, the first index will be used.
 * @param {ydn.db.KeyRange=} keyRange configuration in json or native format.
 * @param {boolean=} reverse reverse.
 * @param {boolean=} unique unique.
 * @param {boolean=} key_only true for key only iterator.
 * @param {Function=} filter filter function.
 * @param {Function=} continued continued function.
 * @extends {ydn.db.sql.req.IterableQuery}
 * @constructor
 */
ydn.db.sql.req.IdbQuery = function(store, index, keyRange,
          reverse, unique, key_only, filter, continued) {

  goog.base(this, store, index, keyRange,  reverse, unique, key_only,
    filter, continued);

};
goog.inherits(ydn.db.sql.req.IdbQuery, ydn.db.sql.req.IterableQuery);


/**
 * @enum {string}
 */
ydn.db.sql.req.IdbQuery.Methods = {
  OPEN: 'op',
  COUNT: 'cn'
};


/**
 *
 * @type {ydn.db.sql.req.IdbQuery.Methods}
 */
ydn.db.sql.req.IdbQuery.prototype.method = ydn.db.sql.req.IdbQuery.Methods.OPEN;





// Input 103
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Some utilities on top of goog.object module.
 *
 */

goog.provide('ydn.string');


/**
 * Split comma sperated tokens. Token can be possibly double quoted token.
 * @param {string} str string to split.
 * @return {Array.<string>} list of tokens.
 */
ydn.string.split_comma_seperated = function(str) {
  return str.match(/(?:"[^"]*"|[^,])+/g);
};


/**
 * Split string by space separated, possibly double quoted, string.
 * @param {string} str string to be split.
 * @return {Array.<string>} resulting split string.
 */
ydn.string.split_space_seperated = function(str) {
  return str.match(/\w+|"[^"]+"/g);
};


/**
 * Split string by space separated, possibly single or double quoted, string.
 * @param {string} str string to be split.
 * @return {Array.<string>} quote preserved tokens.
 */
ydn.string.split_space = function(str) {
  return str.match(/[^\s"']+|"[^"]*"|'[^']*'/g);
};







// Input 104
/**
 * @fileoverview Expression.
 *
 * User: kyawtun
 * Date: 28/10/12
 */


goog.provide('ydn.math.Expression');
goog.require('ydn.string');
goog.require('goog.string');
goog.require('ydn.object');
goog.require('ydn.error');


/**
 * Create a expression by tokesn.
 * @param {Array} tokens
 * @constructor
 */
ydn.math.Expression = function(tokens) {
  /**
   * @final
   * @type {Array}
   */
  this.tokens = tokens;
};


/**
 *
 * @type {Array}
 */
ydn.math.Expression.prototype.tokens = [];


/**
 * Evaluate the expression.
 * @param {Object} with_object
 * @param {...} var_args
 * @return {*}
 */
ydn.math.Expression.prototype.evaluate = function (with_object, var_args) {
  var stack = [];

  for (var i = 0; i < this.tokens.length; i++) {
    var tok = this.tokens[i];
    var is_field_name = tok[0] === '"' && tok[tok.length - 1] === '"';
    var is_string_literal = tok[0] === "'" && tok[tok.length - 1] === "'";

    if (is_field_name) {
      var keys = goog.string.stripQuotes(tok, '"').split('.');
      goog.asserts.assertObject(with_object);
      var value = goog.object.getValueByKeys(with_object, keys);
      stack.push(value);
    } else if (is_string_literal) {
      stack.push(goog.string.stripQuotes(tok, "'"));
    } else if (goog.isString(tok)) {
      if (tok === 'true') {
        stack.push(true);
      } else if (tok === 'false') {
        stack.push(false);
      } else if (tok === 'Date') {
        stack.push(new Date(parseInt(stack.pop(), 10)));
      } else if (tok === 'now') {
        stack.push(new Date());
      } else if (tok === '!') {
        stack[stack.length - 1] = !stack[stack.length - 1];
      } else if (tok === '==') {
        stack.push(stack.pop() == stack.pop());
      } else if (tok === '===') {
        stack.push(stack.pop() === stack.pop());
      } else if (tok === '!=') {
        stack.push(stack.pop() != stack.pop());
      } else if (tok === '!==') {
        stack.push(stack.pop() !== stack.pop());
      } else if (tok === '<=') {
        stack.push(stack.pop() <= stack.pop());
      } else if (tok === '<') {
        stack.push(stack.pop() < stack.pop());
      } else if (tok === '>=') {
        stack.push(stack.pop() >= stack.pop());
      } else if (tok === '>') {
        stack.push(stack.pop() > stack.pop());
      } else if (tok === '&') {
        stack.push(stack.pop() & stack.pop());
      } else if (tok === '|') {
        stack.push(stack.pop() | stack.pop());
      } else if (tok === '?') {
        var ok = !!stack.pop();
        var a = stack.pop();
        var b = stack.pop();
        var v = ok ? a : b;
        //console.log([this.tokens.join(','), ok, a, b, v]);
        stack.push(v);
      } else if (tok === '+') {
        stack.push(stack.pop() + stack.pop());
      } else if (tok === '-') {
        stack.push(stack.pop() - stack.pop());
      } else if (tok === '*') {
        stack.push(stack.pop() * stack.pop());
      } else if (tok === '/') {
        stack.push(stack.pop() / stack.pop());
      } else if (tok === '%') {
        stack.push(stack.pop() % stack.pop());
      } else if (tok === 'at') {
        var at = stack.pop();
        var arr = stack.pop();
        stack.push(arr[at]);
      } else if (tok === 'of') {
        var ele = stack.pop();
        var arr = stack.pop();
        var v = goog.isArray(arr) ? arr.indexOf(ele) : -1;
        stack.push(v);
      } else if (tok === 'in') {
        var ele = stack.pop();
        var arr = stack.pop();
        var v = goog.isArray(arr) ? arr.indexOf(ele) >= 0 : false;
        stack.push(v);
      } else if (tok === 'abs') {
        stack[stack.length - 1] = Math.abs(stack[stack.length - 1]);
      } else if (tok[0] == '$' && /^\$\d$/.test(tok)) {
        var pos = parseInt(tok.match(/^\$(\d)$/)[1], 10);
        stack.push(arguments[pos]);
      } else {
        stack.push(parseFloat(tok));
      }
    } else {
      stack.push(tok);
    }
  }
  return stack[0];
};


/**
 * Compile into function.
 * @return {Function}
 */
ydn.math.Expression.prototype.compile = function() {
  var tokens = this.tokens;
  return function() {
    var args = arguments.length > 2 ?
      Array.prototype.slice(arguments, 2) : undefined;
    return ydn.math.Expression.prototype.evaluate.apply(null, args);
  }
};


/**
 * @override
 * @return {Object}
 */
ydn.math.Expression.prototype.toJSON = function() {
  return {'Tokens': ydn.object.clone(this.tokens)};
};


/**
 * Parse reverse polish notation statement into expression.
 * @param {string} expression
 * @return {!ydn.math.Expression}
 */
ydn.math.Expression.parseRpn = function(expression) {
  return new ydn.math.Expression(ydn.string.split_space(expression));
};

/**
 * Infix notation statement into expression.
 * @param {string} expression
 * @return {!ydn.math.Expression}
 */
ydn.math.Expression.parseInfix = function(expression) {
  throw new ydn.error.NotImplementedException(
    'Too lazy to learn Dutch in Shunting Yard station, ' +
      'we speak Polish here.');
};

// Input 105
// Copyright 2012 YDN Authors, Yathit. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview A SQL node represent .
 *
 * Analyze SQL statement and extract execution scope.
 */


goog.provide('ydn.db.Sql');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.db.schema.Database');
goog.require('ydn.db.sql.req.IdbQuery');
goog.require('ydn.error.ArgumentException');
goog.require('ydn.math.Expression');
goog.require('ydn.string');



/**
 * @param {string} sql The sql statement.
 * @constructor
 */
ydn.db.Sql = function(sql) {

  /*
   * A query has the following form:
   *
   * <Query> := SELECT <SelList> FROM <FromList> WHERE <Condition>
   *
   */

  if (!goog.isString(sql)) {
    throw new ydn.error.ArgumentException();
  }

  this.sql_ = sql;

  /**
   *
   * @type {ydn.db.base.TransactionMode}
   * @private
   */
  this.mode_ = ydn.db.base.TransactionMode.READ_WRITE;

  /**
   * @private
   * @type {Array.<string>}
   */
  this.store_names_ = [];

  this.parseBasic_(sql);

  this.last_error_ = '';
  this.has_parsed_ = false;
};


/**
 * @private
 * @type {string} sql statement.
 */
ydn.db.Sql.prototype.sql_ = '';


/**
 * @private
 * @type {string}
 */
ydn.db.Sql.prototype.modifier_;


/**
 * @private
 * @type {string}
 */
ydn.db.Sql.prototype.condition_;


/**
 *
 * @type {string|undefined}
 * @private
 */
ydn.db.Sql.prototype.aggregate_;


/**
 *
 * @type {string|undefined}
 * @private
 */
ydn.db.Sql.prototype.order_;


/**
 *
 * @type {number}
 * @private
 */
ydn.db.Sql.prototype.limit_ = NaN;


/**
 *
 * @type {number}
 * @private
 */
ydn.db.Sql.prototype.offset_ = NaN;


/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.Sql.prototype.reverse_ = false;


/**
 * @type {string}
 * @private
 */
ydn.db.Sql.prototype.selList_;


/**
 *
 * @type {string}
 * @private
 */
ydn.db.Sql.prototype.last_error_ = '';


/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.Sql.prototype.has_parsed_ = false;


/**
 *
 * @param {string} sql
 * @private
 */
ydn.db.Sql.prototype.parseBasic_ = function(sql) {
  var from_parts = sql.split(/\sFROM\s/i);
  if (from_parts.length != 2) {
    // throw new ydn.db.SqlParseError('FROM required.');
    return;
  }
  var pre_from = from_parts[0];
  var post_from = from_parts[1];

  // Parse Pre-FROM
  var pre_from_parts = pre_from.match(
      /\s*?(SELECT|INSERT|UPDATE|DELETE)\s+(.*)/i);
  if (pre_from_parts.length != 3) {
    // throw new ydn.db.SqlParseError('Unable to parse: ' + sql);
    return;
  }

  // action
  this.action_ = pre_from_parts[1].toUpperCase();
  if (this.action_ == 'SELECT') {
    this.mode_ = ydn.db.base.TransactionMode.READ_ONLY;
  } else if (this.action_ == 'INSERT') {
    this.mode_ = ydn.db.base.TransactionMode.READ_WRITE;
  } else if (this.action_ == 'UPDATE') {
    this.mode_ = ydn.db.base.TransactionMode.READ_WRITE;
  } else if (this.action_ == 'DELETE') {
    this.mode_ = ydn.db.base.TransactionMode.READ_WRITE;
  } else {
    return;
  }

  var selList = pre_from_parts[2].trim();

  var agg = selList.match(/^(MIN|MAX|COUNT|AVG|SUM)/i);
  if (agg) {
    this.aggregate_ = agg[0].toUpperCase();
    selList = selList.replace(/^(MIN|MAX|COUNT|AVG|SUM)/i, '').trim();
  } else {
    this.aggregate_ = undefined;
  }
  // remove parentheses if it has
  if (selList.charAt(0) == '(') {
    if (selList.charAt(selList.length - 1) == ')') {
      selList = selList.substring(1, selList.length - 1);
    } else {
      new ydn.db.SqlParseError('missing closing parentheses');
    }
  }
  this.selList_ = selList;

  // collect modifiers
  var mod_idx = post_from.search(/(ORDER BY|LIMIT|OFFSET)/i);
  if (mod_idx > 0) {
    this.modifier_ = post_from.substring(mod_idx);
    post_from = post_from.substring(0, mod_idx);
  } else {
    this.modifier_ = '';
  }

  // collect condition
  var where_idx = post_from.search(/WHERE/i);
  if (where_idx > 0) {
    this.condition_ = post_from.substring(where_idx + 6).trim();
    post_from = post_from.substring(0, where_idx);
  } else {
    this.condition_ = '';
  }

  var stores = post_from.trim().split(',');
  this.store_names_ = stores.map(function(x) {
    x = goog.string.stripQuotes(x, '"');
    x = goog.string.stripQuotes(x, "'");
    return x.trim();
  });

  this.has_parsed_ = true;
};


/**
 * @param {Array=} params SQL parameters.
 * @return {string} empty if successfully parse
 */
ydn.db.Sql.prototype.parse = function(params) {


  if (params) {
    for (var i = 0; i < params.length; i++) {
      this.sql_ = this.sql_.replace('?', params[i]);
    }
    this.parseBasic_(this.sql_);
  }

  this.wheres_ = this.parseConditions();
  if (!this.wheres_) {
    return this.last_error_;
  }

  var start_idx = this.modifier_.length;

  var offset_result = /OFFSET\s+(\d+)/i.exec(this.modifier_);
  if (offset_result) {
    this.offset_ = parseInt(offset_result[1], 10);
    start_idx = this.modifier_.search(/OFFSET/i);
  }
  var limit_result = /LIMIT\s+(\d+)/i.exec(this.modifier_);
  if (limit_result) {
    this.limit_ = parseInt(limit_result[1], 10);
    var idx = this.modifier_.search(/LIMIT/i);
    if (idx < start_idx) {
      start_idx = idx;
    }
  }
  var order_str = this.modifier_.substr(0, start_idx);
  var order_result = /ORDER BY\s+(.+)/i.exec(order_str);
  if (order_result) {
    var order = order_result[1].trim();
    var asc_desc = order.match(/(ASC|DESC)/i);
    if (asc_desc) {
      this.reverse_ = asc_desc[0].toUpperCase() == 'DESC';
      order = order.replace(/\s+(ASC|DESC)/i, '');
    } else {
      this.reverse_ = false;
    }
    this.order_ = goog.string.stripQuotes(
        goog.string.stripQuotes(order, '"'), "'");
    goog.asserts.assert(this.order_.length > 0, 'Invalid order by field');
  } else {
    this.order_ = undefined;
  }

  this.has_parsed_ = true;
  return '';
};



/**
 * Get select field list.
 * @return {Array.<string>} return null if selection is '*'. Field names are
 * trimmed.
 */
ydn.db.Sql.prototype.getSelList = function() {

  if (this.selList_ == '*') {
    return null;
  } else {
    var fields = this.selList_.split(',');
    fields = fields.map(function(s) {
      return goog.string.stripQuotes(s.trim(), '"');
    });
    return fields;
  }
};


/**
 *
 * @return {string}
 */
ydn.db.Sql.prototype.getSql = function() {
  return this.sql_;
};


/**
 * @inheritDoc
 */
ydn.db.Sql.prototype.toJSON = function() {
  return {
    'sql': this.sql_
  };
};


/**
 *
 * @return {!Array.<string>} store name.
 */
ydn.db.Sql.prototype.getStoreNames = function() {
  return goog.array.clone(this.store_names_);
};


/**
 *
 * @return {ydn.db.base.TransactionMode} store name.
 */
ydn.db.Sql.prototype.getMode = function() {
  return this.mode_;
};


/**
 *
 * @return {number}
 */
ydn.db.Sql.prototype.getLimit = function() {
  return this.limit_;
};


/**
 *
 * @return {number}
 */
ydn.db.Sql.prototype.getOffset = function() {
  return this.offset_;
};


/**
 *
 * @return {string|undefined}
 */
ydn.db.Sql.prototype.getOrderBy = function() {
  return this.order_;
};


/**
 *
 * @return {boolean}
 */
ydn.db.Sql.prototype.isReversed = function() {
  return this.reverse_;
};


/**
 * Get condition as array of Where clause.
 * @return {!Array.<ydn.db.Where>}
 */
ydn.db.Sql.prototype.getConditions = function() {
  return this.wheres_;
};


/**
 * Get condition as array of Where clause.
 * @return {Array.<ydn.db.Where>}
 */
ydn.db.Sql.prototype.parseConditions = function() {
  var wheres = [];
  var re_op = /(.+?)(<=|>=|=|>|<)(.+)/i;

  var findIndex = function(field) {
    return goog.array.findIndex(wheres, function(w) {
      return w.getField() == field;
    });
  };

  if (this.condition_.length > 0) {

    var conds = this.condition_.split('AND');
    for (var i = 0; i < conds.length; i++) {
      var cond = conds[i];
      var result = re_op.exec(cond);
      if (result) {
        var field = result[1].trim();
        field = goog.string.stripQuotes(field, '"');
        field = goog.string.stripQuotes(field, "'");
        if (field.length > 0) {
          var value = result[3].trim();
          if (goog.string.startsWith(value, '"')) {
            value = goog.string.stripQuotes(value, '"');
          } else if (goog.string.startsWith(value, "'")) {
            value = goog.string.stripQuotes(value, "'");
          } else {
            value = parseFloat(value);
            //console.log([cond, result[1], result[2], result[3], value]);
          }

          var op = result[2];
          var where = new ydn.db.Where(field, op, value);
          var ex_idx = findIndex(field);
          if (ex_idx >= 0) {
            wheres[ex_idx] = wheres[ex_idx].and(where);
            if (!wheres[ex_idx]) {
              this.last_error_ = 'where clause "' + cond + '" conflict';
              return null;
            }
          } else {
            wheres.push(where);
          }
        } else {
          this.last_error_ = 'Invalid clause "' + cond + '"';
          return null;
        }
      } else {
        this.last_error_ = 'Invalid clause "' + cond + '"';
        return null;
      }
    }
  }
  return wheres;
};


/**
 *
 * @return {string} store name.
 */
ydn.db.Sql.prototype.getAction = function() {
  return this.action_;
};


/**
 * @override
 */
ydn.db.Sql.prototype.toString = function() {
  if (goog.DEBUG) {
    return 'query:' + this.sql_;
  } else {
    return goog.base(this, 'toString');
  }
};


/**
 * @return {string|undefined} return aggregate or undefined.
 */
ydn.db.Sql.prototype.getAggregate = function() {
  return this.aggregate_;
};



// Input 106
/**
 * @fileoverview Interface for index base request.
 *
 */


goog.provide('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.Sql');
goog.require('ydn.db.Streamer');
goog.require('ydn.db.crud.req.IRequestExecutor');




/**
 * @interface
 * @extends {ydn.db.core.req.IRequestExecutor}
 */
ydn.db.sql.req.IRequestExecutor = function() {};


/**
 * Execute SQL statement.
 * @param {ydn.db.Request} rq
 * @param {!ydn.db.Sql} sql  SQL object.
 * @param {!Array} params SQL parameters.
 */
ydn.db.sql.req.IRequestExecutor.prototype.executeSql = goog.abstractMethod;



// Input 107
/**
 * @fileoverview Interface for executing database request.
 *
 */


goog.provide('ydn.db.sql.IStorage');
goog.require('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.core.IOperator');



/**
 * @extends {ydn.db.core.IOperator}
 * @interface
 */
ydn.db.sql.IStorage = function() {};

//
//
///**
// * @throws {ydn.db.ScopeError}
// * @param {function(!ydn.db.core.req.IRequestExecutor)} callback callback function
// * when request executor is ready.
// * @param {!Array.<string>} store_names store name involved in the transaction.
// * @param {ydn.db.base.TransactionMode} mode mode, default to 'readonly'.
// */
//ydn.db.sql.IStorage.prototype.exec = goog.abstractMethod;



// Input 108
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview IDB query node.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.sql.req.nosql.Node');
goog.require('ydn.db.Iterator');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Sql');
goog.require('ydn.error.ArgumentException');



/**
 * Create a SQL query object from a query object.
 *
 * @param {!ydn.db.schema.Store} schema store schema.
 * @param {!ydn.db.Sql} sql store name.
 * @constructor
 * @struct
 */
ydn.db.sql.req.nosql.Node = function(schema, sql) {

  this.sql = sql;
  this.store_schema = schema;

};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.sql.req.nosql.Node.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.sql.req.nosql.Node');


/**
 * @type {!ydn.db.schema.Store}
 * @protected
 */
ydn.db.sql.req.nosql.Node.prototype.store_schema;


/**
 * @type {ydn.db.Sql}
 * @protected
 */
ydn.db.sql.req.nosql.Node.prototype.sql;


/**
 * @inheritDoc
 */
ydn.db.sql.req.nosql.Node.prototype.toJSON = function() {
  return {'sql': this.sql};
};


/**
 * @override
 */
ydn.db.sql.req.nosql.Node.prototype.toString = function() {
  return 'idb.Node:';
};


/**
 * @param {ydn.db.Request} rq
 * @param {ydn.db.core.req.IRequestExecutor} req
 */
ydn.db.sql.req.nosql.Node.prototype.execute = function(rq, req) {

  var me = this;
  var out = [];

  var store_name = this.sql.getStoreNames()[0];
  var wheres = this.sql.getConditions();
  var limit = this.sql.getLimit();
  limit = isNaN(limit) ? ydn.db.base.DEFAULT_RESULT_LIMIT : limit;
  var offset = this.sql.getOffset();
  offset = isNaN(offset) ? 0 : offset;
  var order = this.sql.getOrderBy();
  var sel_fields = this.sql.getSelList();
  /**
   *
   * @type {IDBKeyRange}
   */
  var key_range = null;
  var reverse = this.sql.isReversed();
  if (wheres.length == 0) {
    key_range = null;
  } else if (wheres.length == 1) {
    key_range = ydn.db.KeyRange.parseIDBKeyRange(wheres[0].getKeyRange());
  } else {
    throw new ydn.debug.error.NotSupportedException('too many conditions.');
  }

  if (!goog.isNull(sel_fields)) {
    rq.await(function(records, is_error, cb) {
      var out = records;
      if (!is_error) {
        out = records.map(function(record) {
          var n = sel_fields.length;
          if (n == 1) {
            return ydn.db.utils.getValueByKeys(record, sel_fields[0]);
          } else {
            var obj = {};
            for (var i = 0; i < n; i++) {
              obj[sel_fields[i]] = ydn.db.utils.getValueByKeys(record,
                  sel_fields[i]);
            }
            return obj;
          }
        });
      }
      cb(out, is_error);
    });
  }

  var index_name = wheres.length > 0 ? wheres[0].getField() : undefined;

  var msg = rq.getLabel() + ' executing on' + store_name;
  if (index_name) {
    msg += ':' + index_name;
  }
  msg += ' ' + ydn.db.KeyRange.toString(key_range);
  this.logger.finer(msg);

  if (order && order != this.store_schema.getKeyPath()) {
    req.list(rq, ydn.db.base.QueryMethod.LIST_VALUE,
        store_name, order, key_range,
        limit, offset, reverse, false);
  } else if (goog.isDef(index_name) && index_name !=
      this.store_schema.getKeyPath()) {
    req.list(rq, ydn.db.base.QueryMethod.LIST_VALUE, store_name, index_name,
        key_range, limit, offset, reverse, false);
  } else {
    req.list(rq, ydn.db.base.QueryMethod.LIST_VALUE, store_name, null,
        key_range, limit, offset, reverse, false);
  }

};





// Input 109
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Execute aggregate query.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.sql.req.nosql.ReduceNode');
goog.require('ydn.db.sql.req.nosql.Node');
goog.require('ydn.object');



/**
 *
 * @param {!ydn.db.schema.Store} schema store schema.
 * @param {!ydn.db.Sql} sql store name.
 * @extends {ydn.db.sql.req.nosql.Node}
 * @constructor
 * @struct
 */
ydn.db.sql.req.nosql.ReduceNode = function(schema, sql) {
  goog.base(this, schema, sql);
};
goog.inherits(ydn.db.sql.req.nosql.ReduceNode, ydn.db.sql.req.nosql.Node);


/**
 * @param {ydn.db.Request} rq transaction object.
 * @param {ydn.db.core.req.IRequestExecutor} req request executor.
 */
ydn.db.sql.req.nosql.ReduceNode.prototype.execute = function(rq, req) {

  var me = this;
  var out;

  var store_name = this.sql.getStoreNames()[0];
  var wheres = this.sql.getConditions();
  /**
   *
   * @type {IDBKeyRange}
   */
  var key_range = null;
  var reverse = this.sql.isReversed();
  if (wheres.length == 0) {
    key_range = null;
  } else if (wheres.length == 1) {
    key_range = ydn.db.KeyRange.parseIDBKeyRange(wheres[0].getKeyRange());
  } else {
    throw new ydn.debug.error.NotSupportedException('too many conditions.');
  }

  var aggregate = this.sql.getAggregate();
  var index_name = wheres.length > 0 ? wheres[0].getField() : undefined;

  var msg = rq.getLabel() + ' executing ' + aggregate + ' on ' + store_name;
  if (index_name) {
    msg += ':' + index_name;
  }
  msg += ' ' + ydn.db.KeyRange.toString(key_range);
  this.logger.finer(msg);

  if (aggregate == 'COUNT') {
    if (key_range) {
      req.countKeyRange(rq, store_name, key_range,
          index_name, false);
    } else {
      req.countKeyRange(rq, store_name, null, undefined, false);
    }
  } else {
    var reduce;
    var fields = this.sql.getSelList();
    if (!fields || fields.length == 0) {
      throw new ydn.error.InvalidOperationError(
          'field name require for reduce operation: ' + aggregate);
    }
    var field_name = fields[0];
    if (aggregate == 'MIN') {
      reduce = ydn.db.sql.req.nosql.ReduceNode.reduceMin(field_name);
    } else if (aggregate == 'MAX') {
      reduce = ydn.db.sql.req.nosql.ReduceNode.reduceMax(field_name);
    } else if (aggregate == 'AVG') {
      out = 0;
      reduce = ydn.db.sql.req.nosql.ReduceNode.reduceAverage(field_name);
    } else if (aggregate == 'SUM') {
      out = 0;
      reduce = ydn.db.sql.req.nosql.ReduceNode.reduceSum(field_name);
    } else {
      throw new ydn.error.NotSupportedException(aggregate);
    }

    // TODO: optimization
    // if (this.store_schema.hasIndex(field_name)) {

    var iter;

    if (goog.isDef(index_name)) {
      iter = new ydn.db.IndexValueIterator(store_name, index_name,
          key_range);
    } else {
      iter = new ydn.db.ValueIterator(store_name, key_range);
    }

    var cur = req.getCursor(rq.getTx(), rq.getLabel(), store_name,
        ydn.db.base.QueryMethod.LIST_VALUE);
    var cursor = iter.load([cur]);

    /**
     *
     * @param {!Error} e
     */
    cursor.onFail = function(e) {
      rq.setDbValue(e, true);
    };
    var i = 0;
    /**
     *
     * @param {IDBKey=} opt_key
     */
    cursor.onNext = function(opt_key) {
      if (goog.isDefAndNotNull(opt_key)) {
        var value = iter.isKeyIterator() ?
            cursor.getPrimaryKey() : cursor.getValue();
        out = reduce(value, out, i);
        cursor.advance(1);
        i++;
      } else {
        rq.setDbValue(out);
      }
    };
  }


};


/**
 * Return reduce iteration function for AVERAGE
 * @param {string} field name.
 * @return {Function} average.
 */
ydn.db.sql.req.nosql.ReduceNode.reduceAverage = function(field) {
  return function(curr, prev, i) {
    if (!goog.isDef(prev)) {
      prev = 0;
    }
    return (prev * i + curr[field]) / (i + 1);
  };
};


/**
 * Return reduce iteration function for SUM
 * @param {string} field field name.
 * @return {Function} sum.
 */
ydn.db.sql.req.nosql.ReduceNode.reduceSum = function(field) {
  return function(curr, prev, i) {
    return prev + curr[field];
  };
};


/**
 * Return reduce iteration function for MIN
 * @param {string} field name.
 * @return {Function} min.
 */
ydn.db.sql.req.nosql.ReduceNode.reduceMin = function(field) {
  return function(curr, prev, i) {
    var x = curr[field];
    if (!goog.isDef(prev)) {
      return x;
    }
    return prev < x ? prev : x;
  };
};


/**
 * Return reduce iteration function for MAX
 * @param {string} field name.
 * @return {Function} max.
 */
ydn.db.sql.req.nosql.ReduceNode.reduceMax = function(field) {
  return function(curr, prev, i) {
    var x = curr[field];
    if (!goog.isDef(prev)) {
      return x;
    }
    return prev > x ? prev : x;
  };
};


// Input 110
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Implements ydn.db.io.QueryService with IndexedDB.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.sql.req.IndexedDb');
goog.require('ydn.db.core.req.IndexedDb');
goog.require('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.sql.req.nosql.Node');
goog.require('ydn.db.sql.req.nosql.ReduceNode');



/**
 * Create a new IDB request executor.
 * @param {string} dbname database name.
 * @extends {ydn.db.core.req.IndexedDb}
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.sql.req.IRequestExecutor}
 * @struct
 */
ydn.db.sql.req.IndexedDb = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.sql.req.IndexedDb, ydn.db.core.req.IndexedDb);


/**
 *
 * @const {boolean} turn on debug flag to dump object.
 */
ydn.db.sql.req.IndexedDb.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.sql.req.IndexedDb.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.sql.req.IndexedDb');


/**
 * @inheritDoc
 */
ydn.db.sql.req.IndexedDb.prototype.executeSql = function(rq, sql, params) {

  var msg = sql.parse(params);
  if (msg) {
    throw new ydn.db.SqlParseError(msg);
  }
  var store_names = sql.getStoreNames();
  if (store_names.length == 1) {
    var store_schema = this.schema.getStore(store_names[0]);
    if (!store_schema) {
      throw new ydn.db.NotFoundError(store_names[0]);
    }
    var fields = sql.getSelList();
    if (fields) {
      for (var i = 0; i < fields.length; i++) {
        if (!store_schema.hasIndex(fields[i])) {
          throw new ydn.db.NotFoundError('Index "' + fields[i] +
              '" not found in ' + store_names[0]);
        }
      }
    }
    var node;
    if (sql.getAggregate()) {
      node = new ydn.db.sql.req.nosql.ReduceNode(store_schema, sql);
    } else {
      node = new ydn.db.sql.req.nosql.Node(store_schema, sql);
    }

    node.execute(rq, this);
  } else {
    throw new ydn.error.NotSupportedException(sql.getSql());
  }
};


// Input 111
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Query object to feed WebSQL iterator.
 *
 *
 */


goog.provide('ydn.db.sql.req.SqlQuery');
goog.require('ydn.db.sql.req.IterableQuery');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.Where');
goog.require('ydn.error.ArgumentException');



/**
 * Create a SQL query object from a query object.
 *
 * This clone given query object and added iteration functions so that
 * query processor can mutation as part of query optimization processes.
 *
 * @param {string} store store name.
 * @param {string=} index store field, where key query is preformed. If not
 * provided, the first index will be used.
 * @param {(ydn.db.KeyRange|ydn.db.IDBKeyRange)=} keyRange configuration in json or native format.
 * @param {boolean=} reverse reverse.
 * @param {boolean=} unique unique.
 * @param {boolean=} key_only true for key only iterator.
 * @param {Function=} filter filter function.
 * @param {Function=} continued continued function.
 * @extends {ydn.db.sql.req.IterableQuery}
 * @constructor
 */
ydn.db.sql.req.SqlQuery = function(store, index, keyRange,
       reverse, unique, key_only, filter, continued) {

  goog.base(this, store, index, keyRange, reverse, unique, key_only,
    filter, continued);

  this.parseRow = ydn.db.sql.req.SqlQuery.prototype.parseRow;
  this.sql = '';
  this.params = [];
};
goog.inherits(ydn.db.sql.req.SqlQuery, ydn.db.sql.req.IterableQuery);



/**
 * @inheritDoc
 */
ydn.db.sql.req.SqlQuery.prototype.toJSON = function() {
  var obj = goog.base(this, 'toJSON');
  obj['sql'] = this.sql;
  obj['params'] = ydn.object.clone(this.params);
  return obj;
};
//
//
//
///**
// * @param {string?} keyPath if index is not defined, keyPath will be used.
// * @param {!Array.<ydn.db.schema.DataType>|ydn.db.schema.DataType|undefined} type data type.
// * @return {{sql: string, params: !Array.<string>}} return equivalent of
// * keyRange
// * to SQL WHERE clause and its parameters.
// */
//ydn.db.sql.req.SqlQuery.prototype.toWhereClause = function(type, keyPath) {
//
//  var idx = this.getIndexName();
//  var index = goog.isDef(idx) ? idx :
//      goog.isDefAndNotNull(keyPath) ? keyPath :
//          ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME;
//  var column = goog.string.quote(index);
//
//  var where = new ydn.db.Where(column, keyPath);
//
//  return where.toWhereClause(type);
//};




/**
 * SQL statement for executing.
 * @type {string} sql string.
 */
ydn.db.sql.req.SqlQuery.prototype.sql = '';


/**
 * SQL parameters for executing SQL.
 * @type {!Array.<string>} sql parameters.
 */
ydn.db.sql.req.SqlQuery.prototype.params = [];




/**
 * @override
 */
ydn.db.sql.req.SqlQuery.prototype.toString = function() {
  var idx = goog.isDef(this.getIndexName()) ? ':' + this.getIndexName() : '';
  return 'Cursor:' + this.getStoreName() + idx;
};



/**
 * Parse resulting object of a row into original object as it 'put' into the
 * database.
 * @param {!Object} row row.
 * @param {ydn.db.schema.Store} store store schema.
 * @return {!Object} parse value.
 */
ydn.db.sql.req.SqlQuery.prototype.parseRow = function(row, store) {
  return ydn.db.crud.req.WebSql.parseRow(row, store);
};


/**
 * Return given input row.
 * @final
 * @param {!Object} row row.
 * @param {ydn.db.schema.Store} store store schema.
 * @return {!Object} the first field of object in row value.
 */
ydn.db.sql.req.SqlQuery.parseRowIdentity = function(row, store) {
  return row;
};

//
///**
// * @final
// * @param {string} op
// * @param {number|string} lv
// * @param {number|string} x
// * @return {boolean}
// */
//ydn.db.sql.req.SqlQuery.op_test = function(op, lv, x) {
//  if (op === '=' || op === '==') {
//    return  x == lv;
//  } else if (op === '===') {
//    return  x === lv;
//  } else if (op === '>') {
//    return  x > lv;
//  } else if (op === '>=') {
//    return  x >= lv;
//  } else if (op === '<') {
//    return  x < lv;
//  } else if (op === '<=') {
//    return  x <= lv;
//  } else if (op === '!=') {
//    return  x != lv;
//  } else {
//    throw new Error('Invalid op: ' + op);
//  }
//};



// Input 112
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview WebSQL query node.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.sql.req.websql.Node');
goog.require('ydn.db.Sql');
goog.require('ydn.db.schema.Store');



/**
 * Create a SQL query object from a query object.
 *
 *
 * @param {!ydn.db.schema.Store} schema store schema.
 * @param {!ydn.db.Sql} sql store name.
 * @constructor
 */
ydn.db.sql.req.websql.Node = function(schema, sql) {

  this.sql = sql;
  this.store_schema_ = schema;
  this.sel_fields_ = sql.getSelList();

};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.sql.req.websql.Node.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.sql.req.websql.Node');


/**
 * @type {!ydn.db.schema.Store}
 * @private
 */
ydn.db.sql.req.websql.Node.prototype.store_schema_;


/**
 * @type {ydn.db.Sql}
 * @protected
 */
ydn.db.sql.req.websql.Node.prototype.sql;


/**
 * @type {Array.<string>}
 * @private
 */
ydn.db.sql.req.websql.Node.prototype.sel_fields_;


/**
 * @inheritDoc
 */
ydn.db.sql.req.websql.Node.prototype.toJSON = function() {
  return {'sql': this.sql.getSql()};
};


/**
 * @override
 */
ydn.db.sql.req.websql.Node.prototype.toString = function() {
  return 'websql.Node:';
};


/**
 *
 * @param {!Object} row
 * @return {*}
 */
ydn.db.sql.req.websql.Node.prototype.parseRow = function(row) {
  if (!this.sel_fields_) {
    return ydn.db.crud.req.WebSql.parseRow(row, this.store_schema_);
  } else if (this.sel_fields_.length == 1) {
    if (goog.isObject(row)) {
      return goog.object.getValueByKeys(row, this.sel_fields_[0]);
    } else {
      return undefined;
    }
  } else {
    var obj = {};
    for (var i = 0; i < this.sel_fields_.length; i++) {
      obj[this.sel_fields_[i]] = goog.object.getValueByKeys(row,
          this.sel_fields_[i]);
    }
    return obj;
  }

};


/**
 * @param {?function(*, boolean=)} df return key in deferred function.
 * @param {SQLTransaction} tx
 * @param {Array} params
 */
ydn.db.sql.req.websql.Node.prototype.execute = function(df, tx, params) {

  var sql_stm = this.sql.getSql();
  var me = this;
  var out = [];

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {
    var n = results.rows.length;
    for (var i = 0; i < n; i++) {
      var row = results.rows.item(i);
      if (goog.isObject(row)) {
        var value = me.parseRow(row);
        out.push(value);
      } else {
        out.push(value);
      }
    }
    df(out);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.sql.req.WebSql.DEBUG) {
      window.console.log([sql_stm, tr, error]);
    }
    me.logger.warning('Sqlite error: ' + error.message);
    df(error, true);
    return true; // roll back
  };

  if (ydn.db.sql.req.WebSql.DEBUG) {
    window.console.log(this + ' open SQL: ' + sql_stm + ' PARAMS:' +
        ydn.json.stringify(params));
  }
  tx.executeSql(sql_stm, params, callback, error_callback);

};





// Input 113
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview About this file.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.sql.req.websql.ReduceNode');
goog.require('ydn.db.sql.req.websql.Node');
goog.require('ydn.object');



/**
 *
 * @param {!ydn.db.schema.Store} schema store schema.
 * @param {!ydn.db.Sql} sql store name.
 * @extends {ydn.db.sql.req.websql.Node}
 * @constructor
 */
ydn.db.sql.req.websql.ReduceNode = function(schema, sql) {
  goog.base(this, schema, sql);
};
goog.inherits(ydn.db.sql.req.websql.ReduceNode, ydn.db.sql.req.websql.Node);


/**
 * @param {?function(*, boolean=)} df key in deferred function.
 * @param {SQLTransaction} tx
 * @param {Array} params
 * @override
 */
ydn.db.sql.req.websql.ReduceNode.prototype.execute = function(df, tx, params) {

  var sql_stm = this.sql.getSql();
  var me = this;
  var out = [];

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var callback = function(transaction, results) {
    var n = results.rows.length;
    if (n == 1) {
      var value = ydn.object.takeFirst(results.rows.item(0));
      df(value);
    } else if (n == 0) {
      df(undefined);
    } else {
      throw new ydn.db.InternalError();
    }

  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   * @return {boolean} true to roll back.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.sql.req.WebSql.DEBUG) {
      window.console.log([sql_stm, tr, error]);
    }
    me.logger.warning('Sqlite error: ' + error.message);
    df(error, true);
    return true; // roll back
  };

  if (ydn.db.sql.req.WebSql.DEBUG) {
    window.console.log(this + ' open SQL: ' + sql_stm + ' PARAMS:' +
        ydn.json.stringify(params));
  }
  tx.executeSql(sql_stm, params, callback, error_callback);

};





// Input 114
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview WebSQL executor.
 *
 * @see http://www.w3.org/TR/webdatabase/
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.sql.req.WebSql');
goog.require('ydn.db.core.req.WebSql');
goog.require('ydn.db.sql.req.SqlQuery');
goog.require('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.sql.req.websql.Node');
goog.require('ydn.db.sql.req.websql.ReduceNode');



/**
 * @extends {ydn.db.core.req.WebSql}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.sql.req.IRequestExecutor}
 */
ydn.db.sql.req.WebSql = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.sql.req.WebSql, ydn.db.core.req.WebSql);


/**
 * @const
 * @type {boolean} debug flag.
 */
ydn.db.sql.req.WebSql.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.sql.req.WebSql.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.sql.req.WebSql');


/**
 * @inheritDoc
 */
ydn.db.sql.req.WebSql.prototype.executeSql = function(rq, sql, params) {

  var store_names = sql.getStoreNames();
  if (store_names.length == 1) {
    var store_schema = this.schema.getStore(store_names[0]);
    if (!store_schema) {
      throw new ydn.db.NotFoundError(store_names[0]);
    }
    var fields = sql.getSelList();
    if (fields) {
      for (var i = 0; i < fields.length; i++) {
        if (!store_schema.hasIndex(fields[i])) {
          throw new ydn.db.NotFoundError('Index "' + fields[i] +
              '" not found in ' + store_names[0]);
        }
      }
    }

    var node;
    if (sql.getAggregate()) {
      node = new ydn.db.sql.req.websql.ReduceNode(store_schema, sql);
    } else {
      node = new ydn.db.sql.req.websql.Node(store_schema, sql);
    }

    /**
     * @param {*} x result.
     * @param {boolean=} opt_error true if error.
     */
    var df = function(x, opt_error) {
      rq.setDbValue(x, opt_error);
    };

    node.execute(df, /** @type {SQLTransaction} */ (rq.getTx()), params);
  } else {
    throw new ydn.error.NotSupportedException(sql.getSql());
  }

};

//
///**
// *
// * @param {SQLTransaction} tx
// * @param {?function(*, boolean=)} df key in deferred function.
// * @param {ydn.db.sql.req.SqlQuery} cursor the cursor.
// * @param {Function} next_callback icursor handler.
// * @param {ydn.db.base.CursorMode?=} mode mode.
// */
//ydn.db.sql.req.WebSql.prototype.openSqlQuery = function(tx, df, cursor, next_callback, mode) {
//
//  var me = this;
//  var sql = cursor.sql;
//
//  var store = this.schema.getStore(cursor.getStoreName());
//
//  /**
//   * @param {SQLTransaction} transaction transaction.
//   * @param {SQLResultSet} results results.
//   */
//  var callback = function(transaction, results) {
//
//    // http://www.w3.org/TR/webdatabase/#database-query-results
//    // Fetching the length might be expensive, and authors are thus encouraged
//    // to avoid using it (or enumerating over the object, which implicitly uses
//    // it) where possible.
//    // for (var row, i = 0; row = results.rows.item(i); i++) {
//    // Unfortunately, such enumerating don't work
//    // RangeError: Item index is out of range in Chrome.
//    // INDEX_SIZE_ERR: DOM Exception in Safari
//    var n = results.rows.length;
//    for (var i = 0; i < n; i++) {
//      var row = results.rows.item(i);
//      var value = {}; // ??
//      var key = undefined;
//      if (goog.isDefAndNotNull(row)) {
//        value = cursor.parseRow(row, store);
//        var key_str = goog.isDefAndNotNull(store.keyPath) ?
//          row[store.keyPath] : row[ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME];
//        key = ydn.db.schema.Index.sql2js(key_str, store.getType());
//
////        if (!goog.isDefAndNotNull(key)) {
////          var msg;
////          if (goog.DEBUG) {
////            msg = 'executing ' + sql + ' return invalid key object: ' +
////              row.toString().substr(0, 80);
////          }
////          throw new ydn.db.InvalidStateError(msg);
////        }
//        var to_continue = !goog.isFunction(cursor.continued) ||
//          cursor.continued(value);
//
//        if (!goog.isFunction(cursor.filter_fn) || cursor.filter_fn(value)) {
//          var peerKeys = [];
//          var peerIndexKeys = [];
//          var peerValues = [];
//          // var tx = mode === 'readwrite' ? tx : null;
//          var icursor = new ydn.db.WebsqlCursor(tx, key, null, value,
//            peerKeys, peerIndexKeys, peerValues);
//          var to_break = next_callback(icursor);
//          icursor.dispose();
//          if (to_break === true) {
//            break;
//          }
//        }
//        if (!to_continue) {
//          break;
//        }
//      }
//
//    }
//    df(undefined);
//
//  };
//
//  /**
//   * @param {SQLTransaction} tr transaction.
//   * @param {SQLError} error error.
//   * @return {boolean} true to roll back.
//   */
//  var error_callback = function(tr, error) {
//    if (ydn.db.core.req.WebSql.DEBUG) {
//      window.console.log([cursor, tr, error]);
//    }
//    me.logger.warning('Sqlite error: ' + error.message);
//    df(error, true);
//    return true; // roll back
//  };
//
//  if (goog.DEBUG) {
//    this.logger.finest(this + ' open SQL: ' + sql + ' PARAMS:' +
//      ydn.json.stringify(cursor.params));
//  }
//  tx.executeSql(sql, cursor.params, callback, error_callback);
//
//};
//


/**
 * Convert keyRange to SQL statement.
 * @param {ydn.db.Iterator} query schema.
 * @return {ydn.db.sql.req.SqlQuery} sql query.
 */
ydn.db.sql.req.WebSql.prototype.planQuery = function(query) {

  var store = this.schema.getStore(query.getStoreName());
  if (!store) {
    throw new ydn.db.SqlParseError('TABLE: ' + query.getStoreName() +
      ' not found.');
  }

  var key_range = query.getKeyRange();

  var sql = new ydn.db.sql.req.SqlQuery(query.getStoreName(), query.getIndexName(),
    key_range, query.isReversed(), query.isUnique(), query.isKeyIterator());

  var select = 'SELECT';

  var idx_name = sql.getIndexName();

  var index = goog.isDef(idx_name) ? store.getIndex(idx_name) : null;

  var key_column = index ? index.getKeyPath() :
    goog.isDefAndNotNull(store.keyPath) ? store.keyPath :
      ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME;
  goog.asserts.assertString(key_column);
  var column = goog.string.quote(key_column);

  var fields = query.isKeyIterator() ? column : '*';
  var from = fields + ' FROM ' + store.getQuotedName();

  var where_clause = '';
  if (key_range) {

    if (ydn.db.Where.resolvedStartsWith(key_range)) {
      where_clause = column + ' LIKE ?';
      sql.params.push(key_range['lower'] + '%');
    } else {
      if (goog.isDefAndNotNull(key_range.lower)) {
        var lowerOp = key_range['lowerOpen'] ? ' > ' : ' >= ';
        where_clause += ' ' + column + lowerOp + '?';
        sql.params.push(key_range.lower);
      }
      if (goog.isDefAndNotNull(key_range['upper'])) {
        var upperOp = key_range['upperOpen'] ? ' < ' : ' <= ';
        var and = where_clause.length > 0 ? ' AND ' : ' ';
        where_clause += and + column + upperOp + '?';
        sql.params.push(key_range.upper);
      }
    }
    where_clause = ' WHERE ' + '(' + where_clause + ')';
  }

  // Note: IndexedDB key range result are always ordered.
  var dir = 'ASC';
  if (query.isReversed()) {
    dir = 'DESC';
  }
  var order = 'ORDER BY ' + column;

  sql.sql = [select, from, where_clause, order, dir].join(' ');
  return sql;
};



// Input 115
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Data store in memory.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.sql.req.SimpleStore');
goog.require('ydn.db.core.req.SimpleStore');
goog.require('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.sql.req.nosql.Node');
goog.require('ydn.db.sql.req.nosql.ReduceNode');



/**
 * @extends {ydn.db.core.req.SimpleStore}
 * @param {string} dbname database name.
 * @param {!ydn.db.schema.Database} schema schema.
 * @constructor
 * @implements {ydn.db.sql.req.IRequestExecutor}
 */
ydn.db.sql.req.SimpleStore = function(dbname, schema) {
  goog.base(this, dbname, schema);
};
goog.inherits(ydn.db.sql.req.SimpleStore, ydn.db.core.req.SimpleStore);


/**
 *
 * @const {boolean} turn on debug flag to dump object.
 */
ydn.db.sql.req.SimpleStore.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.sql.req.SimpleStore.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.sql.req.SimpleStore');


/**
 * @inheritDoc
 */
ydn.db.sql.req.SimpleStore.prototype.executeSql = function(rq, sql, params) {

  var msg = sql.parse(params);
  if (msg) {
    throw new ydn.db.SqlParseError(msg);
  }
  var store_names = sql.getStoreNames();
  if (store_names.length == 1) {
    var store_schema = this.schema.getStore(store_names[0]);
    if (!store_schema) {
      throw new ydn.db.NotFoundError(store_names[0]);
    }
    var fields = sql.getSelList();
    if (fields) {
      for (var i = 0; i < fields.length; i++) {
        if (!store_schema.hasIndex(fields[i])) {
          throw new ydn.debug.error.ArgumentException('Index "' + fields[i] +
              '" not found in ' + store_names[0]);
        }
      }
    }
    var node;
    if (sql.getAggregate()) {
      node = new ydn.db.sql.req.nosql.ReduceNode(store_schema, sql);
    } else {
      node = new ydn.db.sql.req.nosql.Node(store_schema, sql);
    }

    node.execute(rq, this);
  } else {
    throw new ydn.debug.error.NotSupportedException(sql.getSql());
  }
};


// Input 116
/**
* @fileoverview Provide iteration query.
*
*
*/


goog.provide('ydn.db.sql.DbOperator');
goog.require('ydn.db.Iterator');
goog.require('ydn.db.core.DbOperator');
goog.require('ydn.db.sql.IStorage');
goog.require('ydn.db.sql.req.IRequestExecutor');
goog.require('ydn.db.sql.req.IndexedDb');
goog.require('ydn.db.sql.req.WebSql');
goog.require('ydn.db.sql.req.SimpleStore');
goog.require('ydn.debug.error.ArgumentException');



/**
 * Construct storage to execute CRUD database operations.
 *
 * Execution database operation is atomic, if a new transaction require,
 * otherwise existing transaction is used and the operation become part of
 * the existing transaction. A new transaction is required if the transaction
 * is not active or locked. Active transaction can be locked by using
 * mutex.
 *
 * @param {!ydn.db.crud.Storage} storage base storage object.
 * @param {!ydn.db.schema.Database} schema
 * @param {ydn.db.tr.Thread} thread
 * @param {ydn.db.tr.Thread} sync_thread
 * @constructor
 * @implements {ydn.db.sql.IStorage}
 * @extends {ydn.db.core.DbOperator}
*/
ydn.db.sql.DbOperator = function(storage, schema, thread, sync_thread) {
  goog.base(this, storage, schema, thread, sync_thread);
};
goog.inherits(ydn.db.sql.DbOperator, ydn.db.core.DbOperator);


//
///**
// * Explain query plan.
// * @param {!ydn.db.Iterator} q
// * @return {Object} plan in JSON
// */
//ydn.db.sql.DbOperator.prototype.explain = function (q) {
//  if (!this.executor) {
//    return {'error':'database not ready'};
//  } else if (q instanceof ydn.db.Sql) {
//    return this.getExecutor().explainSql(q);
//  } else {
//    throw new ydn.error.ArgumentException();
//  }
//};
/**
 * @param {string} sql SQL statement.
 * @param {!Array=} opt_params SQL parameters.
 * @return {!ydn.db.Request} return result as list.
*/
ydn.db.sql.DbOperator.prototype.executeSql = function(sql, opt_params) {

  var query = new ydn.db.Sql(sql);

  var stores = query.getStoreNames();
  for (var i = 0; i < stores.length; i++) {
    var store = this.schema.getStore(stores[i]);
    if (!store) {
      throw new ydn.debug.error.ArgumentException('store: ' + store +
          ' not exists.');
    }
  }

  this.logger.finer('executeSql: ' + sql + ' params: ' + opt_params);
  var req = this.tx_thread.request(ydn.db.Request.Method.SQL,
      query.getStoreNames(), query.getMode());
  req.addTxback(function() {
    this.getExecutor().executeSql(req, query, opt_params || []);
  }, this);

  return req;
};





// Input 117
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Provide SQL query statement execution.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.sql.Storage');
goog.require('ydn.db.core.Storage');
goog.require('ydn.db.sql.DbOperator');



/**
 * Construct storage providing atomic CRUD database operations on implemented
 * storage mechanisms.
 *
 * This class do not execute database operation, but create a non-overlapping
 * transaction queue on ydn.db.crud.DbOperator and all operations are
 * passed to it.
 *
 *
 * @param {string=} opt_dbname database name.
 * @param {(ydn.db.schema.Database|!DatabaseSchema)=} opt_schema database
 * schema
 * or its configuration in JSON format. If not provided, default empty schema
 * is used.
 * @param {!StorageOptions=} opt_options options.
 * @extends {ydn.db.core.Storage}
 * @implements {ydn.db.sql.IStorage}
 * @constructor
 */
ydn.db.sql.Storage = function(opt_dbname, opt_schema, opt_options) {

  goog.base(this, opt_dbname, opt_schema, opt_options);

};
goog.inherits(ydn.db.sql.Storage, ydn.db.core.Storage);


/**
 *
 * @inheritDoc
 */
ydn.db.sql.Storage.prototype.newOperator = function(tx_thread, sync_thread) {
  return new ydn.db.sql.DbOperator(this, this.schema, tx_thread, sync_thread);
};


/**
 *
 * @return {ydn.db.sql.DbOperator} operator.
 */
ydn.db.sql.Storage.prototype.getSqlOperator = function() {
  return /** @type {ydn.db.sql.DbOperator} */ (this.db_operator);
};


/**
 * @param {string} sql SQL statement.
 * @param {!Array=} opt_params SQL parameters.
 * @return {!goog.async.Deferred} return result as list.
 */
ydn.db.sql.Storage.prototype.executeSql = function(sql, opt_params) {
  return this.getSqlOperator().executeSql(sql, opt_params);
};

//
///**
// * Explain query plan.
// * @param {!ydn.db.Iterator} q
// * @return {Object} plan in JSON
// */
//ydn.db.sql.Storage.prototype.explain = function(q) {
//  return this.base_tx_queue.explain(q);
//};



// Input 118
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Inject request executors.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.sql.Storage.inject');
goog.require('ydn.db.core.Storage');
goog.require('ydn.db.sql.req.IndexedDb');
goog.require('ydn.db.sql.req.SimpleStore');
goog.require('ydn.db.sql.req.WebSql');


/**
 * @return {!ydn.db.sql.req.IRequestExecutor}
 */
ydn.db.sql.Storage.prototype.newExecutor = function() {
  var type = this.getType();
  if (type == ydn.db.base.Mechanisms.IDB) {
    return new ydn.db.sql.req.IndexedDb(this.db_name, this.schema);
  } else if (type == ydn.db.base.Mechanisms.WEBSQL) {
    return new ydn.db.sql.req.WebSql(this.db_name, this.schema);
  } else if (type == ydn.db.base.Mechanisms.MEMORY_STORAGE ||
      type == ydn.db.base.Mechanisms.LOCAL_STORAGE ||
      type == ydn.db.base.Mechanisms.SESSION_STORAGE) {
    return new ydn.db.sql.req.SimpleStore(this.db_name, this.schema);
  } else {
    throw new ydn.debug.error.InternalError('No executor for ' + type);
  }
};

// Input 119
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview IndexedDb connector.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.con.IndexedDb');
goog.require('goog.async.DeferredList');
goog.require('ydn.db');
goog.require('ydn.db.base');
goog.require('ydn.db.con.IDatabase');
goog.require('ydn.db.schema.Database');
goog.require('ydn.error.ConstraintError');
goog.require('ydn.json');



/**
 * @see goog.db.IndexedDb
 * @see ydn.db.Storage for schema
 *
 * @param {number=} opt_size estimated database size.
 * @param {number=} opt_time_out connection time out.
 * @implements {ydn.db.con.IDatabase}
 * @constructor
 * @struct
 */
ydn.db.con.IndexedDb = function(opt_size, opt_time_out) {

  if (goog.isDef(opt_size)) {
    // https://developers.google.com/chrome/whitepapers/storage#asking_more
    // Quota Management API is not IndexedDB API and
    // this should not implement in this database API.
    /*
    webkitStorageInfo.requestQuota(
        webkitStorageInfo.PERSISTENT
        newQuotaInBytes,
        quotaCallback,
        errorCallback);
    */
    if (opt_size > 5 * 1024 * 1024) { // no need to ask for 5 MB.
      this.logger.warning('storage size request ignored, ' +
          'use Quota Management API instead');
    }
  }

  this.idx_db_ = null;

  this.time_out_ = opt_time_out || NaN;

};


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.connect = function(dbname, schema) {

  /**
   * @type {ydn.db.con.IndexedDb}
   */
  var me = this;
  var df = new goog.async.Deferred();
  var old_version = undefined;

  /**
   * This is final result of connection. It is either fail or connected
   * and only once.
   * @param {IDBDatabase} db database instance.
   * @param {Error=} opt_err error.
   */
  var setDb = function(db, opt_err) {

    if (df.hasFired()) {
      me.logger.warning('database already set.');
    } else if (goog.isDef(opt_err)) {
      me.logger.warning(opt_err ? opt_err.message : 'Error received.');
      me.idx_db_ = null;
      df.errback(opt_err);
    } else {
      goog.asserts.assertObject(db, 'db');
      me.idx_db_ = db;
      me.idx_db_.onabort = function(e) {
        me.logger.finest(me + ': abort');
        var request = /** @type {IDBRequest} */ (e.target);
        me.onError(request.error);
      };
      me.idx_db_.onerror = function(e) {
        if (ydn.db.con.IndexedDb.DEBUG) {
          window.console.log(e);
        }
        me.logger.finest(me + ': error');
        var request = /** @type {IDBRequest} */ (e.target);
        me.onError(request.error);
      };

      /**
       * @this {null}
       * @param {IDBVersionChangeEvent} event event.
       */
      me.idx_db_.onversionchange = function(event) {
        // Handle version changes while a web app is open in another tab
        // https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB#
        // Version_changes_while_a_web_app_is_open_in_another_tab
        //
        if (ydn.db.con.IndexedDb.DEBUG) {
          window.console.log([this, event]);
        }
        me.logger.finest(me + ' closing connection for onversionchange to: ' +
            event.version);
        if (me.idx_db_) {
          me.idx_db_.onabort = null;
          me.idx_db_.onblocked = null;
          me.idx_db_.onerror = null;
          me.idx_db_.onversionchange = null;
          me.onVersionChange(event);
          if (!event.defaultPrevented) {
            me.idx_db_.close();
            me.idx_db_ = null;
            var e = new Error();
            e.name = event.type;
            me.onFail(e);
          }
        }
      };
      df.callback(parseFloat(old_version));
    }

  };


  /**
   * Migrate from current version to the given version.
   * @protected
   * @param {IDBDatabase} db database instance.
   * @param {IDBTransaction} trans transaction.
   * @param {boolean} is_caller_setversion call from set version.
   */
  var updateSchema = function(db, trans, is_caller_setversion) {

    var action = is_caller_setversion ? 'changing' : 'upgrading';
    me.logger.finer(action + ' version to ' + db.version +
        ' from ' + old_version);

    // create store that we don't have previously
    for (var i = 0; i < schema.stores.length; i++) {
      // this is sync process.
      me.update_store_(db, trans, schema.stores[i]);
    }

    // delete stores
    var storeNames = /** @type {DOMStringList} */ (db.objectStoreNames);
    for (var n = storeNames.length, i = 0; i < n; i++) {
      if (!schema.hasStore(storeNames[i])) {
        db.deleteObjectStore(storeNames[i]);
        me.logger.finer('store: ' + storeNames[i] + ' deleted.');
      }
    }
  };

  var version = schema.getVersion();

  // In chrome, version is taken as description.
  me.logger.finer('Opening database: ' + dbname + ' ver: ' +
      (schema.isAutoVersion() ? 'auto' : version));

  /**
   * Currently in transaction stage, opening indexedDB return two format.
   * IDBRequest from old and IDBOpenDBRequest from new API.
   * @type {IDBOpenDBRequest|IDBRequest}
   */
  var openRequest;
  if (!goog.isDef(version)) {
    // auto schema do not have version
    // Note: undefined is not 'not defined', i.e. open('name', undefined)
    // is not the same effect as open('name');
    openRequest = ydn.db.base.indexedDb.open(dbname);
  } else {
    openRequest = ydn.db.base.indexedDb.open(dbname, version);
    // version could be number (new) or string (old).
    // casting is for old externs uncorrected defined as string
    // old version will think, version as description.
  }

  openRequest.onsuccess = function(ev) {
    /**
     * @type {IDBDatabase}
     */
    var db = ev.target.result;
    if (!goog.isDef(old_version)) {
      old_version = db.version;
    }
    var msg = 'Database: ' + db.name + ', ver: ' + db.version + ' opened.';
    me.logger.finer(msg);

    if (schema.isAutoVersion()) {
      // since there is no version, auto schema always need to validate
      /**
       * Validate given schema and schema of opened database.
       * @param {ydn.db.schema.Database} db_schema schema.
       */
      var schema_updater = function(db_schema) {

        // add existing object store
        if (schema instanceof ydn.db.schema.EditableDatabase) {
          var editable = /** @type {ydn.db.schema.EditableDatabase} */ (schema);
          for (var i = 0; i < db_schema.stores.length; i++) {
            if (!editable.hasStore(db_schema.stores[i].getName())) {
              editable.addStore(db_schema.stores[i].clone());
            }
          }
        }

        var diff_msg = schema.difference(db_schema, false, true);
        if (diff_msg.length > 0) {
          me.logger.finer('Schema change require for difference in ' +
              diff_msg);

          var on_completed = function(t, e) {
            if (t == ydn.db.base.TxEventTypes.COMPLETE) {
              setDb(db);
            } else {
              me.logger.severe('Fail to update version on ' + db.name + ':' +
                  db.version);
              setDb(null, e);
            }
          };

          var next_version = goog.isNumber(db.version) ? db.version + 1 : 1;

          if ('IDBOpenDBRequest' in goog.global) {
            db.close();
            var req = ydn.db.base.indexedDb.open(
                dbname, /** @type {number} */ (next_version));
            req.onupgradeneeded = function(ev) {
              var db = ev.target.result;
              me.logger.finer('re-open for version ' + db.version);
              updateSchema(db, req['transaction'], false);

            };
            req.onsuccess = function(ev) {
              setDb(ev.target.result);
            };
            req.onerror = function(e) {
              me.logger.finer(me + ': fail.');
              setDb(null);
            };
          } else {
            var ver_request = db.setVersion(next_version + '');

            ver_request.onfailure = function(e) {
              me.logger.warning('migrating from ' + db.version + ' to ' +
                  next_version + ' failed.');
              setDb(null, e);
            };


            var trans = ver_request['transaction'];
            ver_request.onsuccess = function(e) {

              ver_request['transaction'].oncomplete = tr_on_complete;

              updateSchema(db, ver_request['transaction'], true);
            };

            var tr_on_complete = function(e) {

              // for old format.
              // by reopening the database, we make sure that we are not in
              // version change state since transaction cannot open during
              // version change state.
              // db.close(); // necessary - cause error ?
              var reOpenRequest = ydn.db.base.indexedDb.open(dbname);
              reOpenRequest.onsuccess = function(rev) {
                var db = rev.target.result;
                me.logger.finer(me + ': OK.');
                setDb(db);
              };

              reOpenRequest.onerror = function(e) {
                me.logger.finer(me + ': fail.');
                setDb(null);
              };
            };

            if (goog.isDefAndNotNull(ver_request['transaction'])) {
              ver_request['transaction'].oncomplete = tr_on_complete;
            }

          }

        } else {
          setDb(db);
        }
      };
      me.getSchema(schema_updater, undefined, db);

    } else if (schema.getVersion() > db.version) {

      // in old format, db.version will be a string. type coercion should work
      // here

      goog.asserts.assertFunction(db['setVersion'],
          'Expecting IDBDatabase in old format');
      var version = /** @type {*} */ (schema.getVersion());
      var ver_request = db.setVersion(/** @type {string} */ (version));

      ver_request.onfailure = function(e) {
        me.logger.warning('migrating from ' + db.version + ' to ' +
            schema.getVersion() + ' failed.');
        setDb(null, e);
      };
      ver_request.onsuccess = function(e) {
        updateSchema(db, ver_request['transaction'], true);
      };
    } else {
      if (schema.getVersion() == db.version) {
        me.logger.finer('database version ' + db.version + ' ready to go');
      } else {
        // this will not happen according to IDB spec.
        me.logger.warning('connected database version ' + db.version +
            ' is higher than requested version.');
      }

      /**
       * Validate given schema and schema of opened database.
       * @param {ydn.db.schema.Database} db_schema schema.
       */
      var validator = function(db_schema) {
        var diff_msg = schema.difference(db_schema, false, true);
        if (diff_msg.length > 0) {
          me.logger.finer(diff_msg);
          setDb(null, new ydn.error.ConstraintError('different schema: ' +
              diff_msg));
        } else {
          setDb(db);
        }
      };

      me.getSchema(validator, undefined, db);

    }
  };

  openRequest.onupgradeneeded = function(ev) {
    var db = ev.target.result;
    old_version = NaN;
    me.logger.finer('upgrade needed for version ' + db.version);
    updateSchema(db, openRequest['transaction'], false);
  };

  openRequest.onerror = function(ev) {
    var ver = goog.isDef(schema.version) ?
        ' with version ' + schema.version : '';
    var msg = 'open request to database "' + dbname + '" ' + ver +
        ' cause error of ' + openRequest.error.name;
    if (ydn.db.con.IndexedDb.DEBUG) {
      window.console.log([ev, openRequest]);
    }
    me.logger.severe(msg);
    setDb(null, ev);
  };

  openRequest.onblocked = function(ev) {
    if (ydn.db.con.IndexedDb.DEBUG) {
      window.console.log([ev, openRequest]);
    }
    me.logger.severe('database ' + dbname + ' ' + schema.version +
        ' block, close other connections.');

    // should we reopen again after some time?
    setDb(null, ev);
  };

  // check for long database connection
  if (goog.isNumber(this.time_out_) && !isNaN(this.time_out_)) {
    setTimeout(function() {
      if (openRequest.readyState != 'done') {
        // what we observed is chrome attached error object to openRequest
        // but did not call any of over listening events.
        var msg = me + ': database state is still ' + openRequest.readyState;
        me.logger.severe(msg);
        setDb(null, new ydn.db.TimeoutError('connection timeout after ' +
            me.time_out_));
      }
    }, this.time_out_);

  }

  return df;

};


/**
 * @protected
 * @define {boolean} turn on debug flag to dump object.
 */
ydn.db.con.IndexedDb.DEBUG = false;


/**
 * @final
 * @return {boolean} return indexedDB support on run time.
 */
ydn.db.con.IndexedDb.isSupported = function() {
  return !!ydn.db.base.indexedDb;
};


/**
 * Timeout.
 * @type {number}
 * @private
 */
ydn.db.con.IndexedDb.prototype.time_out_ = 3 * 60 * 1000;


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.onFail = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.onError = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.onVersionChange = function(e) {};


/**
 * @return {string} storage mechanism type.
 */
ydn.db.con.IndexedDb.prototype.getType = function() {
  return ydn.db.base.Mechanisms.IDB;
};


/**
 * Return database object, on if it is ready.
 * @final
 * @return {IDBDatabase} this instance.
 */
ydn.db.con.IndexedDb.prototype.getDbInstance = function() {
  // no checking for closing status. caller should know it.
  return this.idx_db_ || null;
};


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.isReady = function() {
  return !!this.idx_db_;
};


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.con.IndexedDb.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.con.IndexedDb');


/**
 * @private
 * @type {IDBDatabase}
 */
ydn.db.con.IndexedDb.prototype.idx_db_ = null;


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.getVersion = function() {
  return this.idx_db_ ? parseFloat(this.idx_db_.version) : undefined;
};


/**
 * @inheritDoc
 */
ydn.db.con.IndexedDb.prototype.getSchema = function(callback, trans, db) {

  // console.log(this + ' getting schema');
  /**
   * @type {IDBDatabase}
   */
  var idb = /** @type {IDBDatabase} */ (db) || this.idx_db_;
  var mode = ydn.db.base.TransactionMode.READ_ONLY;
  if (!goog.isDef(trans)) {
    var names = [];
    for (var i = idb.objectStoreNames.length - 1; i >= 0; i--) {
      names[i] = idb.objectStoreNames[i];
    }
    if (names.length == 0) {
      // http://www.w3.org/TR/IndexedDB/#widl-IDBDatabase-transaction-
      // IDBTransaction-any-storeNames-DOMString-mode
      //
      // InvalidAccessError: The function was called with an empty list of
      // store names

      callback(new ydn.db.schema.Database(idb.version));
      return;
    }
    trans = idb.transaction(names, /** @type {number} */ (mode));
  } else if (goog.isNull(trans)) {
    if (idb.objectStoreNames.length == 0) {
      callback(new ydn.db.schema.Database(idb.version));
      return;
    } else {
      throw new ydn.error.InternalError();
    }
  } else {
    //window.console.log(['trans', trans]);
    idb = trans['db'];
  }

  /** @type {DOMStringList} */
  var objectStoreNames = /** @type {DOMStringList} */ (idb.objectStoreNames);

  var stores = [];
  var n = objectStoreNames.length;
  for (var i = 0; i < n; i++) {
    /**
     * @type {IDBObjectStore}
     */
    var objStore = trans.objectStore(objectStoreNames[i]);
    var indexes = [];
    for (var j = 0, ni = objStore.indexNames.length; j < ni; j++) {
      /**
       * @type {IDBIndex}
       */
      var index = objStore.index(objStore.indexNames[j]);

      indexes[j] = new ydn.db.schema.Index(index.keyPath, undefined,
          index.unique, index.multiEntry, index.name);
    }
    stores[i] = new ydn.db.schema.Store(objStore.name, objStore.keyPath,
        objStore.autoIncrement, undefined, indexes);
  }
  var schema = new ydn.db.schema.Database(/** @type {number} */ (idb.version),
      stores);

  callback(schema);
};


/**
 *
 * @param {IDBDatabase} db database.
 * @param {IDBTransaction} trans transaction.
 * @param {ydn.db.schema.Store} store_schema store schema.
 * @private
 */
ydn.db.con.IndexedDb.prototype.update_store_ = function(db, trans,
                                                        store_schema) {
  this.logger.finest('Creating Object Store for ' + store_schema.getName() +
      ' keyPath: ' + store_schema.getKeyPath());

  var objectStoreNames = /** @type {DOMStringList} */ (db.objectStoreNames);

  /**
   * @return {IDBObjectStore}
   */
  var createAObjectStore = function() {
    // IE10 is picky on optional parameters of keyPath. If it is undefined,
    // it must not be defined.
    var options = {'autoIncrement': !!store_schema.isAutoIncrement()};
    if (goog.isDefAndNotNull(store_schema.getKeyPath())) {
      options['keyPath'] = store_schema.getKeyPath();
    }
    // try/cache don't add benefit.
    // try {
    return db.createObjectStore(store_schema.getName(), options);
    // } catch (e) {
    //   if (goog.DEBUG && e.name == 'InvalidAccessError') {
    //     throw new ydn.db.InvalidAccessError('creating store for ' +
    //         store_schema.getName() + ' of keyPath: ' +
    //         store_schema.getKeyPath() + ' and autoIncrement: ' +
    //         store_schema.isAutoIncrement());
    //   } else if (goog.DEBUG && e.name == 'ConstraintError') {
    //     // store already exist.
    //     throw new ydn.error.ConstraintError('creating store for ' +
    //         store_schema.getName());
    //   } else {
    //     throw e;
    //   }
    // }
  };

  /**
   * @type {IDBObjectStore}
   */
  var store;
  if (objectStoreNames.contains(store_schema.getName())) {
    // already have the store, just update indexes

    store = trans.objectStore(store_schema.getName());

    var keyPath = store_schema.getKeyPath() || '';
    var store_keyPath = store.keyPath || '';

    if (!!ydn.db.schema.Index.compareKeyPath(keyPath, store_keyPath)) {
      db.deleteObjectStore(store_schema.getName());
      this.logger.warning('store: ' + store_schema.getName() +
          ' deleted due to keyPath change.');
      store = createAObjectStore();
    } else if (goog.isBoolean(store.autoIncrement) &&
        goog.isBoolean(store_schema.isAutoIncrement()) &&
        store.autoIncrement != store_schema.isAutoIncrement()) {
      db.deleteObjectStore(store_schema.getName());
      this.logger.warning('store: ' + store_schema.getName() +
          ' deleted due to autoIncrement change.');
      store = createAObjectStore();
    } else {

      var indexNames = /** @type {DOMStringList} */ (store.indexNames);

      var created = 0;
      var deleted = 0;
      var modified = 0;
      for (var j = 0; j < store_schema.countIndex(); j++) {
        var index = store_schema.index(j);
        var need_create = false;
        if (indexNames.contains(index.getName())) {
          var store_index = store.index(index.getName());
          // NOTE: Some browser (read: IE10) does not expose multiEntry
          // attribute in the index object.
          var dif_unique = goog.isDefAndNotNull(store_index.unique) &&
              goog.isDefAndNotNull(index.unique) &&
              store_index.unique != index.unique;
          var dif_multi = goog.isDefAndNotNull(store_index.multiEntry) &&
              goog.isDefAndNotNull(index.multiEntry) &&
              store_index.multiEntry != index.multiEntry;
          var dif_key_path = goog.isDefAndNotNull(store_index.keyPath) &&
              goog.isDefAndNotNull(index.keyPath) &&
              !!ydn.db.schema.Index.compareKeyPath(
                  store_index.keyPath, index.keyPath);
          if (dif_unique || dif_multi || dif_key_path) {
            // console.log('delete index ' + index.name + ' on ' + store.name);
            store.deleteIndex(index.getName());
            need_create = true;
            created--;
            modified++;
          }
        } else if (index.getType() != ydn.db.schema.DataType.BLOB) {
          // BLOB column data type, used in websql, is not index.
          need_create = true;
        }
        if (need_create) {
          if (index.unique || index.multiEntry) {
            var idx_options = {
              unique: index.unique,
              multiEntry: index.multiEntry};
            store.createIndex(index.getName(),
                // todo: remove this casting after externs is updated.
                /** @type  {string} */ (index.keyPath),
                idx_options);
          } else {
            store.createIndex(index.getName(),
                /** @type  {string} */ (index.keyPath));
          }
          created++;
        }
      }
      for (var j = 0; j < indexNames.length; j++) {
        if (!store_schema.hasIndex(indexNames[j])) {
          store.deleteIndex(indexNames[j]);
          deleted++;
        }
      }

      this.logger.finest('Updated store: ' + store.name + ', ' + created +
          ' index created, ' + deleted + ' index deleted, ' +
          modified + ' modified.');
    }

  } else {

    store = createAObjectStore();

    for (var j = 0; j < store_schema.countIndex(); j++) {
      var index = store_schema.index(j);

      if (index.getType() == ydn.db.schema.DataType.BLOB) {
        this.logger.info('Index ' + index + ' of blob data type ignored.');
        continue;
      }
      this.logger.finest('Creating index: ' + index);

      if (index.unique || index.multiEntry) {
        var idx_options = {unique: index.unique, multiEntry: index.multiEntry};

        store.createIndex(index.getName(), index.keyPath, idx_options);
      } else {
        store.createIndex(index.getName(), index.keyPath);
      }
    }

    this.logger.finest('Created store: ' + store);
  }
};


/**
 * When DB is ready, fnc will be call with a fresh transaction object. Fnc must
 * put the result to 'result' field of the transaction object on success. If
 * 'result' field is not set, it is assumed
 * as failed.
 * @protected
 * @param {function(IDBTransaction)|Function} fnc transaction function.
 * @param {Array.<string>} scopes list of stores involved in the
 * transaction. If null, all stores is used.
 * @param {ydn.db.base.TransactionMode} mode mode.
 * @param {function(ydn.db.base.TxEventTypes, *)} on_completed
 * on complete  handler.
 */
ydn.db.con.IndexedDb.prototype.doTransaction = function(fnc, scopes, mode,
    on_completed) {

  /**
   *
   * @type {IDBDatabase}
   */
  var db = this.idx_db_;

  if (!scopes) {
    scopes = [];
    for (var i = db.objectStoreNames.length - 1; i >= 0; i--) {
      scopes[i] = db.objectStoreNames[i];
    }
  }

  if (scopes.length == 0) {
    fnc(null); // should we just throw error?
    return;
    // opening without object store name will cause InvalidAccessError
  }

  var tx = db.transaction(scopes, /** @type {number} */ (mode));

  tx.oncomplete = function(event) {
    on_completed(ydn.db.base.TxEventTypes.COMPLETE, event);
  };

  // NOTE: Let downstream `tr` module handle transaction error event.
  // future more, database instance will receive and dispatch error event.
  // tx.onerror = function(event) {};

  tx.onabort = function(event) {
    on_completed(ydn.db.base.TxEventTypes.ABORT, event);
  };

  fnc(tx);
  fnc = null;

};


/**
 * Close the connection.
 */
ydn.db.con.IndexedDb.prototype.close = function() {
  this.logger.finest(this + ' closing connection');
  this.idx_db_.close(); // IDB return void.
};


if (goog.DEBUG) {
  /**
   * @override
   */
  ydn.db.con.IndexedDb.prototype.toString = function() {
    var s = this.idx_db_ ? this.idx_db_.name + ':' + this.idx_db_.version : '';
    return 'IndexedDB:' + s;
  };


  /**
   * Handy debug function in testing on Chrome to delete all IDB databases.
   */
  ydn.db.con.IndexedDb.deleteAllDatabases = function() {
    var req = goog.global.indexedDB['webkitGetDatabaseNames']();
    req.onsuccess = function(e) {
      var names = e.target.result;
      for (var i = 0; i < names.length; i++) {
        window.console.info('deleting ' + names[i]);
        ydn.db.base.indexedDb.deleteDatabase(names[i]);
      }
    };
  };
}


/**
 * Delete database.
 * @param {string} db_name name of database.
 * @param {string=} opt_type delete only specific types.
 * @return {ydn.db.Request}
 */
ydn.db.con.IndexedDb.deleteDatabase = function(db_name, opt_type) {
  if (ydn.db.base.indexedDb &&
      (!opt_type || opt_type == ydn.db.base.Mechanisms.IDB)) {
    var req = ydn.db.base.indexedDb.deleteDatabase(db_name);
    var df = new ydn.db.Request(ydn.db.Request.Method.VERSION_CHANGE);
    req.onblocked = function(e) {
      df.notify(e);
    };
    req.onerror = function(e) {
      df.errback(e);
    };
    req.onsuccess = function(e) {
      df.callback(e);
    };
    return df;
  } else {
    return null;
  }
};
ydn.db.databaseDeletors.push(ydn.db.con.IndexedDb.deleteDatabase);


// Input 120
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Data store in memory.
 */


goog.provide('ydn.db.con.SimpleStorage');
goog.require('goog.asserts');
goog.require('goog.async.Deferred');
goog.require('ydn.db.Key');
goog.require('ydn.db.VersionError');
goog.require('ydn.db.con.IDatabase');
goog.require('ydn.db.con.simple');
goog.require('ydn.db.con.simple.IStorageProvider');
goog.require('ydn.db.con.simple.Store');
goog.require('ydn.db.con.simple.TxStorage');
goog.require('ydn.db.req.InMemoryStorage');
goog.require('ydn.debug.error.InternalError');



/**
 * @implements {ydn.db.con.IDatabase}
 * @param {!ydn.db.con.simple.IStorageProvider=} opt_provider storage provider.
 * @constructor
 * @extends {ydn.db.con.SimpleStorageService}
 * @struct
 */
ydn.db.con.SimpleStorage = function(opt_provider) {
  goog.base(this, opt_provider);

  this.version_ = NaN;

};
goog.inherits(ydn.db.con.SimpleStorage, ydn.db.con.SimpleStorageService);


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.con.SimpleStorage.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.con.SimpleStorage');


/**
 * @private
 * @type {number}
 */
ydn.db.con.SimpleStorage.prototype.version_;


/**
 *
 * @return {boolean} true if memory is supported.
 */
ydn.db.con.SimpleStorage.isSupported = function() {
  return true;
};


/**
 *
 * @type {boolean} debug flag. should always be false.
 */
ydn.db.con.SimpleStorage.DEBUG = false;


/**
 * @type {number}
 * @private
 */
ydn.db.con.SimpleStorage.prototype.version_;


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.getVersion = function() {
  return this.version_;
};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.connect = function(dbname, schema) {

  var me = this;
  var df = new goog.async.Deferred();
  /**
   *
   * @param {number} x
   * @param {Error=} opt_err
   */
  var callDf = function(x, opt_err) {
    setTimeout(function() {
      if (opt_err) {
        me.logger.finer(me + ' opening fail');
        df.errback(opt_err);
      } else {
        me.logger.finer(me + ' version ' + me.getVersion() + ' open');
        df.callback(x);
      }
    }, 10);
  };

  /**
   * @final
   */
  this.storage_ = this.provider_.connectDb(dbname);

  /**
   * @final
   */
  this.dbname = dbname;

  /**
   * @final
   */
  this.schema = schema;

  var db_key = ydn.db.con.simple.makeKey(this.dbname);

  this.version_ = NaN;

  /**
   *
   * @type {DatabaseSchema}
   */
  var ex_schema_json = /** @type {DatabaseSchema} */
      (ydn.json.parse(this.storage_.getItem(db_key)));
  if (goog.isDef(ex_schema_json.version)
      && !goog.isNumber(ex_schema_json.version)) {
    ex_schema_json.version = NaN; // NaN is not serializable.
  }

  if (ex_schema_json) {
    var ex_schema = new ydn.db.schema.Database(ex_schema_json);

    var diff_msg = this.schema.difference(ex_schema, false, false);
    if (diff_msg) {
      if (!this.schema.isAutoVersion() &&
          !isNaN(ex_schema.getVersion()) &&
          this.schema.getVersion() > ex_schema.getVersion()) {
        var msg = goog.DEBUG ? 'existing version ' + ex_schema.getVersion() +
            ' is larger than ' + this.schema.getVersion() : '';
        callDf(NaN, new ydn.db.VersionError(msg));
      } else {
        // upgrade schema
        var v = this.schema.getVersion();
        this.version_ = goog.isDef(v) ? v : (ex_schema.getVersion() + 1);
        for (var i = 0; i < this.schema.count(); i++) {
          var store = this.schema.store(i);
        }
        if (this.schema instanceof ydn.db.schema.EditableDatabase) {
          for (var i = 0; i < ex_schema.count(); i++) {
            var store = ex_schema.store(i);
            goog.asserts.assert(!goog.isNull(store), 'store at ' + i +
                ' is null');
            this.schema.addStore(store);
          }
        }
        var schema_json = this.schema.toJSON();
        schema_json.version = this.version_ || NaN;
        this.storage_.setItem(db_key, ydn.json.stringify(schema_json));
        callDf(ex_schema.getVersion() || NaN);
      }
    } else {
      for (var i = 0; i < this.schema.count(); i++) {
        var store = this.schema.store(i);
      }
      this.version_ = ex_schema.getVersion() || NaN;
      callDf(this.version_);
    }
  } else {
    var json = schema.toJSON();
    this.version_ = 1;
    var old_version = NaN;
    json.version = this.version_;
    this.storage_.setItem(db_key, ydn.json.stringify(json));
    callDf(old_version);
  }

  return df;
};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.isReady = function() {
  return !!this.dbname;
};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.getDbInstance = function() {
  return this.storage_ || null;
};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.onFail = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.onError = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.onVersionChange = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.getType = function() {
  return 'memory';
};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.close = function() {

};


/**
 * @inheritDoc
 */
ydn.db.con.SimpleStorage.prototype.doTransaction = function(trFn, scopes, mode,
    oncompleted) {
  var tx = new ydn.db.con.simple.TxStorage(this, function(t, e) {
    oncompleted(t, e);
  });
  trFn(tx);
};


if (goog.DEBUG) {
  /**
   * @override
   */
  ydn.db.con.SimpleStorage.prototype.toString = function() {
    var s = this.dbname + ':' + this.version_;
    return 'SimpleStorage:' + this.getType() + ':' + s;
  };
}


// Input 121
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Web storage connectors.
 */

goog.provide('ydn.db.con.LocalStorage');
goog.provide('ydn.db.con.SessionStorage');
goog.require('ydn.db.con.SimpleStorage');



/**
 * @extends {ydn.db.con.SimpleStorage}
 * name and keyPath.
 * @constructor
 * @implements {ydn.db.con.simple.IStorageProvider}
 * @struct
 */
ydn.db.con.LocalStorage = function() {
  goog.base(this, this);
};
goog.inherits(ydn.db.con.LocalStorage, ydn.db.con.SimpleStorage);


/**
 * @inheritDoc
 */
ydn.db.con.LocalStorage.prototype.connectDb = function(name) {
  goog.asserts.assertObject(window.localStorage);
  return window.localStorage;
};


/**
 *
 * @return {boolean} true if localStorage is supported.
 */
ydn.db.con.LocalStorage.isSupported = function() {
  return !!window.localStorage;
};


/**
 * @inheritDoc
 */
ydn.db.con.LocalStorage.prototype.getType = function() {
  return ydn.db.base.Mechanisms.LOCAL_STORAGE;
};


/**
 *
 * @param {string} db_name
 * @param {string=} opt_type delete only specific types.
 */
ydn.db.con.LocalStorage.deleteDatabase = function(db_name, opt_type) {
  if (!!opt_type && opt_type != ydn.db.base.Mechanisms.LOCAL_STORAGE) {
    return;
  }
  var db = new ydn.db.con.LocalStorage();
  var schema = new ydn.db.schema.EditableDatabase();
  db.connect(db_name, schema);
  db.getSchema(function(sch) {
    for (var i = 0; i < sch.stores.length; i++) {
      var store = db.getSimpleStore(sch.stores[i].getName());
      store.clear();
    }
  });
};
ydn.db.databaseDeletors.push(ydn.db.con.LocalStorage.deleteDatabase);



/**
 * @extends {ydn.db.con.SimpleStorage}
 * name and keyPath.
 * @constructor
 * @implements {ydn.db.con.simple.IStorageProvider}
 * @struct
 */
ydn.db.con.SessionStorage = function() {
  goog.base(this, this);
};
goog.inherits(ydn.db.con.SessionStorage, ydn.db.con.SimpleStorage);


/**
 * @inheritDoc
 */
ydn.db.con.SessionStorage.prototype.connectDb = function(name) {
  goog.asserts.assertObject(window.sessionStorage);
  return window.sessionStorage;
};


/**
 *
 * @return {boolean} true if localStorage is supported.
 */
ydn.db.con.SessionStorage.isSupported = function() {
  return !!window.sessionStorage;
};


/**
 * @inheritDoc
 */
ydn.db.con.SessionStorage.prototype.getType = function() {
  return ydn.db.base.Mechanisms.SESSION_STORAGE;
};


/**
 *
 * @param {string} db_name
 * @param {string=} opt_type delete only specific types.
 */
ydn.db.con.SessionStorage.deleteDatabase = function(db_name, opt_type) {
  if (!!opt_type && opt_type != ydn.db.base.Mechanisms.SESSION_STORAGE) {
    return;
  }
  var db = new ydn.db.con.SessionStorage();
  var schema = new ydn.db.schema.EditableDatabase();
  db.connect(db_name, schema);
  db.getSchema(function(sch) {
    for (var i = 0; i < sch.stores.length; i++) {
      var store = db.getSimpleStore(sch.stores[i].getName());
      store.clear();
    }
  });
};
ydn.db.databaseDeletors.push(ydn.db.con.SessionStorage.deleteDatabase);



// Input 122
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview WebSQL database connector.
 *
 * @see http://www.w3.org/TR/webdatabase/
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.con.WebSql');
goog.require('goog.async.Deferred');
goog.require('goog.debug.Logger');
goog.require('goog.functions');
goog.require('ydn.db.SecurityError');
goog.require('ydn.db.base');
goog.require('ydn.db.con.IDatabase');
goog.require('ydn.debug.error.NotImplementedException');
goog.require('ydn.json');
goog.require('ydn.string');



/**
 * Construct a WebSql database connector.
 * Note: Version is ignored, since it does work well.
 * @param {number=} opt_size estimated database size. Default to 5 MB.
 * @implements {ydn.db.con.IDatabase}
 * @constructor
 * @struct
 */
ydn.db.con.WebSql = function(opt_size) {

  // Safari default limit is slightly over 4 MB, so we ask the largest storage
  // size but, still not don't bother to user.
  // Opera don't ask user even request for 1 GB.
  /**
   * @private
   * @final
   * @type {number}
   */
  this.size_ = goog.isDef(opt_size) ? opt_size : 4 * 1024 * 1024; // 5 MB

};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.connect = function(dbname, schema) {

  var description = dbname;

  /**
   * @type {ydn.db.con.WebSql}
   */
  var me = this;

  var old_version = NaN;
  var init_migrated = false;
  var df = new goog.async.Deferred();

  /**
   *
   * @param {Database} db database.
   * @param {Error=} e error object only in case of error.
   */
  var setDb = function(db, e) {
    if (goog.isDef(e)) {
      me.sql_db_ = null;
      df.errback(e);

    } else {
      me.sql_db_ = db;
      df.callback(parseFloat(old_version));
    }
  };


  /**
   * Migrate from current version to the new version.
   * @private
   * @param {Database} db database.
   * @param {ydn.db.schema.Database} schema  schema.
   * @param {boolean=} is_version_change version change or not.
   */
  var doVersionChange_ = function(db, schema, is_version_change) {

    var action = is_version_change ? 'changing version' : 'setting version';

    var current_version = db.version ? parseInt(db.version, 10) : 0;
    var new_version = schema.isAutoVersion() ?
        is_version_change ? isNaN(current_version) ?
            1 : (current_version + 1) : current_version : schema.version;
    me.logger.fine(dbname + ': ' + action + ' from ' +
        db.version + ' to ' + new_version);

    var executed = false;
    var updated_count = 0;

    /**
     * SQLTransactionCallback
     * @param {!SQLTransaction} tx transaction object.
     */
    var transaction_callback = function(tx) {
      // sniff current table info in the database.
      me.getSchema(function(existing_schema) {
        executed = true;
        for (var i = 0; i < schema.count(); i++) {
          var counter = function(ok) {
            if (ok) {
              updated_count++;
            }
          };
          var table_info = existing_schema.getStore(schema.store(i).getName());
          // hint to sniffed schema, so that some lost info are recovered.
          var hinted_store_schema = table_info ?
              table_info.hintForWebSql(schema.store(i)) : null;

          me.update_store_with_info_(tx, schema.store(i), counter,
              hinted_store_schema);
        }

        for (var j = 0; j < existing_schema.count(); j++) {
          var info_store = existing_schema.store(j);
          if (!schema.hasStore(info_store.getName())) {
            if (schema instanceof ydn.db.schema.EditableDatabase) {
              var edited_schema = schema;
              edited_schema.addStore(info_store);
            } else {
              var sql = 'DROP TABLE ' + info_store.getQuotedName();
              me.logger.finer(sql);
              tx.executeSql(sql, [],
                  function(tr) {
                    // ok
                  }, function(tx, e) {
                    throw e;
                  });
            }
          }
        }

      }, tx, db);
    };

    /**
     * SQLVoidCallback
     */
    var success_callback = function() {
      var has_created = updated_count == schema.stores.length;
      if (!executed) {
        // success callback without actually executing
        me.logger.warning(dbname + ': ' + action + ' voided.');
        //if (!me.df_sql_db_.hasFired()) { // FIXME: why need to check ?
        // this checking is necessary when browser prompt user,
        // this migration function run two times: one creating table
        // and one without creating table. How annoying ?
        // testing is in /test/test_multi_storage.html page.
      } else {
        var msg = '.';
        if (updated_count != schema.stores.length) {
          msg = ' but unexpected stores exists.';
        }
        me.logger.finest(dbname + ':' + db.version + ' ready' + msg);
        setDb(db);
      }
    };

    /**
     * SQLTransactionErrorCallback
     * @param {SQLError} e error.
     */
    var error_callback = function(e) {
      me.logger.severe('SQLError ' + e + ' ' + e.code + '(' + e.message + ') ' +
          'while changing version from ' + db.version + ' to ' + new_version +
          ' on ' + dbname);
      if (ydn.db.con.WebSql.DEBUG) {
        window.console.log(e);
      }
      throw e;
    };

    // db.transaction(transaction_callback, error_callback, success_callback);
    db.changeVersion(db.version, new_version + '', transaction_callback,
        error_callback, success_callback);

  };

  /**
   * @type {Database}
   */
  var db = null;

  var creationCallback = function(e) {
    var msg = init_migrated ?
        ' and already migrated, but migrating again.' : ', migrating.';
    me.logger.finest('receiving creation callback ' + msg);

    // the standard state that we should call VERSION_CHANGE request on
    // this callback.
    // http://www.w3.org/TR/webdatabase/#dom-opendatabase
    var use_version_change_request = true;

    //if (!init_migrated) {
    // yeah, to make sure.
    doVersionChange_(db, schema, use_version_change_request);
    //}
  };

  try {
    /**
     * http://www.w3.org/TR/webdatabase/#dom-opendatabase
     *
     * Opening robust web database is tricky. Mainly due to the fact that
     * an empty database is created even if user deny to create the database.
     */
    var version = schema.isAutoVersion() ? '' : schema.version + '';

    // From the W3C description:
    // <snap>
    // If the database version provided is not the empty string, and there is
    // already a database with the given name from the origin origin, but the
    // database has a different version than the version provided, then throw
    // an INVALID_STATE_ERR exception and abort these steps.
    // </snap>
    //
    // Since we have no way of knowing, the database with different version
    // already exist in user browser, opening a version database with specific
    // version is unwise.
    //
    // Interestingly chrome and (Safari on OS X) do not emmit INVALID_STATE_ERR
    // even if the database already exist. It simply invokes creationCallback,
    // as it should.
    //
    // Hence, always open with empty string database version.
    db = goog.global.openDatabase(dbname, '', description, this.size_);
  } catch (e) {
    if (e.name == 'SECURITY_ERR') {
      this.logger.warning('SECURITY_ERR for opening ' + dbname);
      db = null; // this will purge the tx queue
      // throw new ydn.db.SecurityError(e);
      // don't throw now, so that web app can handle without using
      // database.
      this.last_error_ = new ydn.db.SecurityError(e);
    } else {
      // this should never happen.
      throw e;
    }
  }

  if (!db) {
    setDb(null, this.last_error_);
  } else {

    // Even if db version are the same, we cannot assume schema are as expected.
    // Sometimes database is just empty with given version.

    // in case previous database fail, but user granted in next refresh.
    // In this case, empty database of the request version exist,
    // but no tables.

    // WebSQL return limbo database connection,
    // if user haven't decieted whether to allow to deny the storage.
    // the limbo database connection do not execute transaction.

    // version change concept in WebSQL is broken.
    // db.transaction request can alter or create table, which suppose to
    // be done only with db.changeVersion request.

    // the approach we taking here is, we still honour visioning of database
    // but, we do not assume, opening right version will have correct
    // schema as expected. If not correct, we will correct to the schema,
    // without increasing database version.

    old_version = db.version;

    var db_info = 'database ' + dbname;

    if (goog.isDefAndNotNull(schema.version) && schema.version == db.version) {
      me.logger.fine('Existing ' + db_info + ' opened as requested.');
      setDb(db);
    } else {
      // require upgrade check
      this.getSchema(function(existing_schema) {
        var msg = schema.difference(existing_schema, true, false);
        if (msg) {
          if (db.version.length == 0) {
            me.logger.fine('New ' + db_info + ' created.');

            doVersionChange_(db, schema, true);
          } else if (!schema.isAutoVersion()) {
            me.logger.fine('Existing ' + db_info + ' opened and ' +
                ' schema change to version ' + schema.version + ' for ' + msg);

            doVersionChange_(db, schema, true);
          } else {
            me.logger.fine('Existing ' + db_info + ' opened and ' +
                'schema change for ' + msg);

            doVersionChange_(db, schema, true);
          }

        } else {
          // same schema.
          me.logger.fine('Existing ' + db_info + ' with same schema opened.');
          setDb(db);
        }
      }, null, db);
    }

  }

  return df;
};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.getType = function() {
  return ydn.db.base.Mechanisms.WEBSQL;
};


/**
 *
 * @type {Error} error.
 * @private
 */
ydn.db.con.WebSql.prototype.last_error_ = null;


/**
 * @type {Database} database instance.
 * @private
 */
ydn.db.con.WebSql.prototype.sql_db_ = null;


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.getDbInstance = function() {
  return this.sql_db_ || null;
};


/**
 *
 * @return {boolean} true if supported.
 */
ydn.db.con.WebSql.isSupported = function() {
  return goog.isFunction(goog.global.openDatabase);
};


/**
 * @const
 * @type {boolean} debug flag.
 */
ydn.db.con.WebSql.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.con.WebSql.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.con.WebSql');


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.onFail = function(e) {};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.onError = function(e) {};


/**
 * Initialize variable to the schema and prepare SQL statement for creating
 * the table.
 * @private
 * @param {ydn.db.schema.Store} table table schema.
 * @return {!Array.<string>} SQL statement for creating the table.
 */
ydn.db.con.WebSql.prototype.prepareCreateTable_ = function(table) {


  // prepare schema
  var primary_type = table.getSqlType();

  var insert_statement = 'CREATE TABLE IF NOT EXISTS ';
  var sql = insert_statement + table.getQuotedName() + ' (';

  var q_primary_column = table.getSQLKeyColumnNameQuoted();
  sql += q_primary_column + ' ' + primary_type +
      ' PRIMARY KEY ';

  if (table.autoIncrement) {
    sql += ' AUTOINCREMENT ';
  }


  // table must has a default field to store schemaless fields, unless
  // fixed table schema is used.
  if (!table.isFixed() ||
      // note: when even when using fixed schema, blob data are store in
      // default column when store is out-of-line non-indexing
      (!table.usedInlineKey()) && table.countIndex() == 0) {
    sql += ' ,' + ydn.db.base.DEFAULT_BLOB_COLUMN + ' ' +
        ydn.db.schema.DataType.BLOB;
  }

  var sqls = [];
  var sep = ', ';
  var column_names = [q_primary_column];

  for (var i = 0, n = table.countIndex(); i < n; i++) {
    /**
     * @type {ydn.db.schema.Index}
     */
    var index = table.index(i);
    var unique = '';
    if (index.isMultiEntry()) {
      // create separate table for multiEntry
      var idx_name = ydn.db.base.PREFIX_MULTIENTRY +
          table.getName() + ':' + index.getName();
      var idx_unique = index.isUnique() ? ' UNIQUE ' : '';
      var multi_entry_sql = insert_statement +
          goog.string.quote(idx_name) + ' (' +
          q_primary_column + ' ' + primary_type + ', ' +
          index.getSQLIndexColumnNameQuoted() + ' ' + index.getSqlType() +
          idx_unique + ')';
      sqls.push(multi_entry_sql);
      continue;
    } else if (index.isUnique()) {
      unique = ' UNIQUE ';
    }

    // http://sqlite.org/lang_createindex.html
    // http://www.sqlite.org/lang_createtable.html
    // Indexing just the column seems like counter productive. ?
    /*
     INTEGER PRIMARY KEY columns aside, both UNIQUE and PRIMARY KEY constraints
     are implemented by creating an index in the database (in the same way as a
     "CREATE UNIQUE INDEX" statement would). Such an index is used like any
     other index in the database to optimize queries. As a result, there often
     no advantage (but significant overhead) in creating an index on a set of
     columns that are already collectively subject to a UNIQUE or PRIMARY KEY
     constraint.
     */
    var key_path = index.getKeyPath();
    if (index.type != ydn.db.schema.DataType.BLOB && goog.isString(key_path)) {
      var idx_sql = 'CREATE ' + unique + ' INDEX IF NOT EXISTS ' +
          // table name is suffix to index name to satisfy unique index name
          // requirement within a database.
          goog.string.quote(table.getName() + '-' + index.getName()) +
          ' ON ' + table.getQuotedName() +
          ' (' + goog.string.quote(key_path) + ')';
      sqls.push(idx_sql);
    }

    var index_key_path = index.getSQLIndexColumnNameQuoted();

    if (column_names.indexOf(index_key_path) == -1) {
      // store keyPath can also be indexed in IndexedDB spec

      sql += sep + index_key_path + ' ' + index.getSqlType() +
          unique;
      column_names.push(index_key_path);
    }

  }

  sql += ')';
  sqls.unshift(sql);

  return sqls;
};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.getVersion = function() {
  return this.sql_db_ ? parseFloat(this.sql_db_.version) : undefined;
};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.getSchema = function(callback, trans, db) {

  var me = this;
  db = db || this.sql_db_;

  var version = (db && db.version) ?
      parseFloat(db.version) : undefined;
  version = isNaN(version) ? undefined : version;

  /**
   * @final
   * @type {!Array.<ydn.db.schema.Store>}
   */
  var stores = [];

  /**
   * @param {SQLTransaction} transaction transaction.
   * @param {SQLResultSet} results results.
   */
  var success_callback = function(transaction, results) {

    if (!results || !results.rows) {
      return;
    }
    for (var i = 0; i < results.rows.length; i++) {

      var info = /** @type {SqliteTableInfo} */ (results.rows.item(i));
      // console.log(info);

//      name: "st1"
//      rootpage: 5
//      sql: "CREATE TABLE "st1" ("id" TEXT UNIQUE PRIMARY KEY ,
//                                 _default_ undefined )"
//      tbl_name: "st1"
//      type: "table"

//      name: "sqlite_autoindex_st1_1"
//      rootpage: 6
//      sql: null
//      tbl_name: "st1"
//      type: "index"

      if (info.name == '__WebKitDatabaseInfoTable__') {
        continue;
      }
      if (info.name == 'sqlite_sequence') {
        // internal table used by Sqlite
        // http://www.sqlite.org/fileformat2.html#seqtab
        continue;
      }
      if (info.type == 'table') {
        var sql = goog.object.get(info, 'sql');
        me.logger.finest('Parsing table schema from SQL: ' + sql);
        var str = sql.substr(sql.indexOf('('), sql.lastIndexOf(')'));
        var column_infos = ydn.string.split_comma_seperated(str);

        var store_key_path = undefined;
        var key_type;
        var indexes = [];
        var autoIncrement = false;
        var has_default_blob_column = false;

        for (var j = 0; j < column_infos.length; j++) {

          var fields = ydn.string.split_space_seperated(column_infos[j]);
          var upper_fields = goog.array.map(fields, function(x) {
            return x.toUpperCase();
          });
          var name = goog.string.stripQuotes(fields[0], '"');
          var type = ydn.db.schema.Index.toType(upper_fields[1]);
          // console.log([fields[1], type]);

          if (upper_fields.indexOf('PRIMARY') != -1 &&
              upper_fields.indexOf('KEY') != -1) {
            key_type = type;
            if (goog.isString(name) && !goog.string.isEmpty(name) &&
                name != ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME) {
              // console.log('PRIMARY ' + name + ' on ' + info.name);
              // Array key path is denoted by comma separated list.
              var arr_path = name.split(',');
              store_key_path = name;
              if (arr_path.length > 1) {
                store_key_path = arr_path;
                key_type = undefined;
              }
            }
            if (upper_fields.indexOf('AUTOINCREMENT') != -1) {
              autoIncrement = true;
            }
          } else if (name == ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME) {
            // pass, multi entry store use it as non-unique index key.
          } else if (name == ydn.db.base.DEFAULT_BLOB_COLUMN) {
            has_default_blob_column = true;
          } else {
            var unique = upper_fields[2] == 'UNIQUE';
            if (goog.string.startsWith(name, info.tbl_name + '-')) {
              name = name.substr(info.tbl_name.length + 1);
            }
            var index = new ydn.db.schema.Index(name, type, unique);
            // console.log(index);
            indexes.push(index);
          }
        }

        // multiEntry store, which store in separated table
        if (goog.string.startsWith(info.name,
            ydn.db.base.PREFIX_MULTIENTRY)) {
          var names = info.name.split(':');
          if (names.length >= 3) {
            var st_name = names[1];
            var multi_index = new ydn.db.schema.Index(names[2], type,
                unique, true);
            var ex_index = goog.array.findIndex(indexes, function(x) {
              return x.getName() == names[2];
            });
            if (ex_index >= 0) {
              indexes[ex_index] = multi_index;
            } else {
              indexes.push(multi_index);
            }
            var store_index = goog.array.findIndex(stores, function(x) {
              return x.getName() === st_name;
            });
            if (store_index >= 0) { // main table exist, add this index
              var ex_store = stores[store_index];
              stores[store_index] = new ydn.db.schema.Store(ex_store.getName(),
                  ex_store.getKeyPath(), autoIncrement,
                  key_type, indexes, undefined, !has_default_blob_column);
            } else { // main table don't exist, create a temporary table

              stores.push(new ydn.db.schema.Store(st_name, undefined, false,
                  undefined, [multi_index]));
            }
            me.logger.finest('multi entry index "' + multi_index.getName() +
                '" found in ' + st_name + (store_index == -1 ? '*' : ''));
          } else {
            me.logger.warning('Invalid multiEntry store name "' + info.name +
                '"');
          }
        } else {
          var i_store = goog.array.findIndex(stores, function(x) {
            return x.getName() === info.name;
          });
          if (i_store >= 0) {
            var ex_index = stores[i_store].index(0);
            goog.asserts.assertInstanceof(ex_index, ydn.db.schema.Index);
            indexes.push(ex_index);
            stores[i_store] = new ydn.db.schema.Store(info.name, store_key_path,
                autoIncrement, key_type, indexes, undefined,
                !has_default_blob_column);
          } else {
            var store = new ydn.db.schema.Store(info.name, store_key_path,
                autoIncrement, key_type, indexes, undefined,
                !has_default_blob_column);
            stores.push(store);
          }
        }

        //console.log([info, store]);
      }
    }

    var out = new ydn.db.schema.Database(version, stores);
    // console.log(out.toJSON());
    callback(out);
  };

  /**
   * @param {SQLTransaction} tr transaction.
   * @param {SQLError} error error.
   */
  var error_callback = function(tr, error) {
    if (ydn.db.con.WebSql.DEBUG) {
      window.console.log([tr, error]);
    }
    throw error;
  };

  if (!trans) {

    var tx_error_callback = function(e) {
      me.logger.severe('opening tx: ' + e.message);
      throw e;
    };

    db.readTransaction(function(tx) {
      me.getSchema(callback, tx, db);
    }, tx_error_callback, success_callback);

    return;
  }

  // var sql = 'PRAGMA table_info(' + goog.string.quote(table_name) + ')';
  // Invoking this will result error of:
  //   "could not prepare statement (23 not authorized)"

  var sql = 'SELECT * FROM sqlite_master';

  trans.executeSql(sql, [], success_callback, error_callback);
};


/**
 *
 * @param {SQLTransaction} trans transaction.
 * @param {ydn.db.schema.Store} store_schema schema.
 * @param {function(boolean)} callback callback on finished.
 * @private
 */
ydn.db.con.WebSql.prototype.update_store_ = function(trans, store_schema,
                                                     callback) {
  var me = this;
  this.getSchema(function(table_infos) {
    var table_info = table_infos.getStore(store_schema.getName());
    me.update_store_with_info_(trans, store_schema,
        callback, table_info);
  }, trans);
};


/**
 * Alter or create table with given table schema.
 * @param {SQLTransaction} trans transaction.
 * @param {ydn.db.schema.Store} table_schema table schema to be upgrade.
 * @param {function(boolean)?} callback callback on finished. return true
 * if table is updated.
 * @param {ydn.db.schema.Store|undefined} existing_table_schema table
 * information in the existing database.
 * @private
 */
ydn.db.con.WebSql.prototype.update_store_with_info_ = function(trans,
    table_schema, callback, existing_table_schema) {

  var me = this;

  var count = 0;

  var exe_sql = function(sql) {
    /**
     * @param {SQLTransaction} transaction transaction.
     * @param {SQLResultSet} results results.
     */
    var success_callback = function(transaction, results) {
      count++;
      if (count == sqls.length) {
        callback(true);
        callback = null; // must call only once.
      }
    };

    /**
     * @param {SQLTransaction} tr transaction.
     * @param {SQLError} error error.
     */
    var error_callback = function(tr, error) {
      if (ydn.db.con.WebSql.DEBUG) {
        window.console.log([tr, error]);
      }
      count++;
      if (count == sqls.length) {
        callback(false); // false for no change
        callback = null; // must call only once.
      }
      var msg = goog.DEBUG ? 'SQLError creating table: ' +
          table_schema.getName() + ' ' + error.message + ' for executing "' +
          sql : '"';
      throw new ydn.db.SQLError(error, msg);
    };

    trans.executeSql(sql, [], success_callback, error_callback);
  };

  var sqls = this.prepareCreateTable_(table_schema);

  var action = 'Create';
  if (existing_table_schema) {
    // table already exists.
    var msg = table_schema.difference(existing_table_schema);
    if (msg.length == 0) {
      me.logger.finest('same table ' + table_schema.getName() + ' exists.');
      callback(true);
      callback = null;
      return;
    } else {
      action = 'Modify';

      // ALTER TABLE cannot run in WebSQL
      this.logger.warning(
          'table: ' + table_schema.getName() + ' has changed by ' + msg +
          ' ALTER TABLE cannot run in WebSql, dropping old table.');
      sqls.unshift('DROP TABLE IF EXISTS ' +
          goog.string.quote(table_schema.getName()));
    }
  }

  if (ydn.db.con.WebSql.DEBUG) {
    window.console.log([sqls, existing_table_schema]);
  }

  me.logger.finest(action + ' table: ' + table_schema.getName() + ': ' +
      sqls.join(';'));
  for (var i = 0; i < sqls.length; i++) {
    exe_sql(sqls[i]);
  }

};


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.isReady = function() {
  return !!this.sql_db_;
};


/**
 * @final
 */
ydn.db.con.WebSql.prototype.close = function() {
  // WebSQl API do not have close method.
  this.sql_db_ = null;
};


/**
 * @inheritDoc
 * @protected
 */
ydn.db.con.WebSql.prototype.doTransaction = function(trFn, scopes, mode,
                                                     completed_event_handler) {

  var me = this;

  /**
   * SQLTransactionCallback
   * @param {!SQLTransaction} tx transaction.
   */
  var transaction_callback = function(tx) {
    trFn(tx);
  };

  /**
   * SQLVoidCallback
   */
  var success_callback = function() {
    completed_event_handler(ydn.db.base.TxEventTypes.COMPLETE,
        {'type': ydn.db.base.TxEventTypes.COMPLETE});
  };

  /**
   * SQLTransactionErrorCallback
   * @param {SQLError} e error.
   */
  var error_callback = function(e) {
    me.logger.finest(me + ': Tx ' + mode + ' request cause error.');
    // NOTE: we have to call ABORT, instead of ERROR, here.
    // IndexedDB API use COMPLETE or ABORT as promise callbacks.
    // ERROR is just an event.
    completed_event_handler(ydn.db.base.TxEventTypes.ABORT, e);
  };

  if (goog.isNull(this.sql_db_)) {
    // this happen on SECURITY_ERR
    trFn(null);
    // NOTE: we have to call ABORT, instead of ERROR, here. See above.
    completed_event_handler(ydn.db.base.TxEventTypes.ABORT,
        this.last_error_);
  }

  if (mode == ydn.db.base.TransactionMode.READ_ONLY) {
    this.sql_db_.readTransaction(transaction_callback,
        error_callback, success_callback);
  } else if (mode == ydn.db.base.TransactionMode.VERSION_CHANGE) {
    var next_version = this.sql_db_.version + 1;
    this.sql_db_.changeVersion(this.sql_db_.version, next_version + '',
        transaction_callback, error_callback, success_callback);
  } else {
    this.sql_db_.transaction(transaction_callback,
        error_callback, success_callback);
  }

};


/**
 *
 * @param {string} db_name database name to be deleted.
 * @param {string=} opt_type delete only specific types.
 */
ydn.db.con.WebSql.deleteDatabase = function(db_name, opt_type) {
  if (!!opt_type && opt_type != ydn.db.base.Mechanisms.WEBSQL) {
    return;
  }
  // WebSQL API does not expose deleting database.
  // Dropping all tables indeed delete the database.
  var db = new ydn.db.con.WebSql();
  var schema = new ydn.db.schema.EditableDatabase();
  db.logger.finer('deleting websql database: ' + db_name);
  var df = db.connect(db_name, schema);

  var on_completed = function(t, e) {
    db.logger.info('all tables in ' + db_name + ' deleted.');
  };

  df.addCallback(function() {

    db.doTransaction(function delete_tables(tx) {

      /**
       * @param {SQLTransaction} transaction transaction.
       * @param {SQLResultSet} results results.
       */
      var success_callback = function(transaction, results) {
        if (!results || !results.rows) {
          return;
        }
        var n = results.rows.length;
        var del = 0;
        for (var i = 0; i < n; i++) {
          var info = /** @type {SqliteTableInfo} */ (results.rows.item(i));
          if (info.name == '__WebKitDatabaseInfoTable__' ||
              info.name == 'sqlite_sequence') {
            continue;
          }
          del++;
          db.logger.finest('deleting table: ' + info.name);
          tx.executeSql('DROP TABLE ' + info.name);
        }
        db.logger.finer(del + ' tables deleted from "' + db_name + '"');
      };

      /**
       * @param {SQLTransaction} tr transaction.
       * @param {SQLError} error error.
       */
      var error_callback = function(tr, error) {
        if (ydn.db.con.WebSql.DEBUG) {
          window.console.log([tr, error]);
        }
        throw error;
      };

      var sql = 'SELECT * FROM sqlite_master WHERE type = "table"';

      tx.executeSql(sql, [], success_callback, error_callback);

    }, [], ydn.db.base.TransactionMode.READ_WRITE, on_completed);

  });
  df.addErrback(function() {
    db.logger.warning('Connecting ' + db_name + ' failed.');
  });
};
ydn.db.databaseDeletors.push(ydn.db.con.WebSql.deleteDatabase);


/**
 * @inheritDoc
 */
ydn.db.con.WebSql.prototype.onVersionChange = function(e) {};


if (goog.DEBUG) {
  /**
   * @override
   */
  ydn.db.con.WebSql.prototype.toString = function() {
    var s = this.sql_db_ ? ':' + this.sql_db_.version : '';
    return 'WebSql:' + s;
  };
}

// Input 123
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Inject database instance.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */


goog.provide('ydn.db.tr.Storage.inject_db');
goog.require('ydn.db.con.IndexedDb');
goog.require('ydn.db.con.LocalStorage');
goog.require('ydn.db.con.SessionStorage');
goog.require('ydn.db.con.SimpleStorage');
goog.require('ydn.db.con.WebSql');
goog.require('ydn.db.tr.Storage');


/**
 * Create database instance.
 * @protected
 * @param {string} db_type database type.
 * @return {ydn.db.con.IDatabase} newly created database instance.
 */
ydn.db.tr.Storage.prototype.createDbInstance = function(db_type) {

  if (db_type == ydn.db.base.Mechanisms.IDB &&
      ydn.db.con.IndexedDb.isSupported()) {
    return new ydn.db.con.IndexedDb(this.size, this.connectionTimeout);
  } else if (db_type == ydn.db.base.Mechanisms.WEBSQL &&
      ydn.db.con.WebSql.isSupported()) {
    return new ydn.db.con.WebSql(this.size);
  } else if (db_type == ydn.db.base.Mechanisms.LOCAL_STORAGE &&
      ydn.db.con.LocalStorage.isSupported()) {
    return new ydn.db.con.LocalStorage();
  } else if (db_type == ydn.db.base.Mechanisms.SESSION_STORAGE &&
      ydn.db.con.SessionStorage.isSupported()) {
    return new ydn.db.con.SessionStorage();
  } else if (db_type == ydn.db.base.Mechanisms.MEMORY_STORAGE) {
    return new ydn.db.con.SimpleStorage();
  }
  return null;
};

// Input 124
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A global registry for entry points into a program,
 * so that they can be instrumented. Each module should register their
 * entry points with this registry. Designed to be compiled out
 * if no instrumentation is requested.
 *
 * Entry points may be registered before or after a call to
 * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
 * later, the existing monitor will instrument the new entry point.
 *
 * @author nicksantos@google.com (Nick Santos)
 */

goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');

goog.require('goog.asserts');



/**
 * @interface
 */
goog.debug.EntryPointMonitor = function() {};


/**
 * Instruments a function.
 *
 * @param {!Function} fn A function to instrument.
 * @return {!Function} The instrumented function.
 */
goog.debug.EntryPointMonitor.prototype.wrap;


/**
 * Try to remove an instrumentation wrapper created by this monitor.
 * If the function passed to unwrap is not a wrapper created by this
 * monitor, then we will do nothing.
 *
 * Notice that some wrappers may not be unwrappable. For example, if other
 * monitors have applied their own wrappers, then it will be impossible to
 * unwrap them because their wrappers will have captured our wrapper.
 *
 * So it is important that entry points are unwrapped in the reverse
 * order that they were wrapped.
 *
 * @param {!Function} fn A function to unwrap.
 * @return {!Function} The unwrapped function, or {@code fn} if it was not
 *     a wrapped function created by this monitor.
 */
goog.debug.EntryPointMonitor.prototype.unwrap;


/**
 * An array of entry point callbacks.
 * @type {!Array.<function(!Function)>}
 * @private
 */
goog.debug.entryPointRegistry.refList_ = [];


/**
 * Monitors that should wrap all the entry points.
 * @type {!Array.<!goog.debug.EntryPointMonitor>}
 * @private
 */
goog.debug.entryPointRegistry.monitors_ = [];


/**
 * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
 * Checking this allows the compiler to optimize out the registrations.
 * @type {boolean}
 * @private
 */
goog.debug.entryPointRegistry.monitorsMayExist_ = false;


/**
 * Register an entry point with this module.
 *
 * The entry point will be instrumented when a monitor is passed to
 * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
 * entry point is instrumented immediately.
 *
 * @param {function(!Function)} callback A callback function which is called
 *     with a transforming function to instrument the entry point. The callback
 *     is responsible for wrapping the relevant entry point with the
 *     transforming function.
 */
goog.debug.entryPointRegistry.register = function(callback) {
  // Don't use push(), so that this can be compiled out.
  goog.debug.entryPointRegistry.refList_[
      goog.debug.entryPointRegistry.refList_.length] = callback;
  // If no one calls monitorAll, this can be compiled out.
  if (goog.debug.entryPointRegistry.monitorsMayExist_) {
    var monitors = goog.debug.entryPointRegistry.monitors_;
    for (var i = 0; i < monitors.length; i++) {
      callback(goog.bind(monitors[i].wrap, monitors[i]));
    }
  }
};


/**
 * Configures a monitor to wrap all entry points.
 *
 * Entry points that have already been registered are immediately wrapped by
 * the monitor. When an entry point is registered in the future, it will also
 * be wrapped by the monitor when it is registered.
 *
 * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
 */
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
  goog.debug.entryPointRegistry.monitorsMayExist_ = true;
  var transformer = goog.bind(monitor.wrap, monitor);
  for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
    goog.debug.entryPointRegistry.refList_[i](transformer);
  }
  goog.debug.entryPointRegistry.monitors_.push(monitor);
};


/**
 * Try to unmonitor all the entry points that have already been registered. If
 * an entry point is registered in the future, it will not be wrapped by the
 * monitor when it is registered. Note that this may fail if the entry points
 * have additional wrapping.
 *
 * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
 *     the entry points.
 * @throws {Error} If the monitor is not the most recently configured monitor.
 */
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
  var monitors = goog.debug.entryPointRegistry.monitors_;
  goog.asserts.assert(monitor == monitors[monitors.length - 1],
      'Only the most recent monitor can be unwrapped.');
  var transformer = goog.bind(monitor.unwrap, monitor);
  for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
    goog.debug.entryPointRegistry.refList_[i](transformer);
  }
  monitors.length--;
};

// Input 125
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Datastructure: Pool.
 *
 *
 * A generic class for handling pools of objects that is more efficient than
 * goog.structs.Pool because it doesn't maintain a list of objects that are in
 * use. See constructor comment.
 */


goog.provide('goog.structs.SimplePool');

goog.require('goog.Disposable');



/**
 * A generic pool class. Simpler and more efficient than goog.structs.Pool
 * because it doesn't maintain a list of objects that are in use. This class
 * has constant overhead and doesn't create any additional objects as part of
 * the pool management after construction time.
 *
 * IMPORTANT: If the objects being pooled are arrays or maps that can have
 * unlimited number of properties, they need to be cleaned before being
 * returned to the pool.
 *
 * Also note that {@see goog.object.clean} actually allocates an array to clean
 * the object passed to it, so simply using this function would defy the
 * purpose of using the pool.
 *
 * @param {number} initialCount Initial number of objects to populate the
 *     free pool at construction time.
 * @param {number} maxCount Maximum number of objects to keep in the free pool.
 * @constructor
 * @extends {goog.Disposable}
 */
goog.structs.SimplePool = function(initialCount, maxCount) {
  goog.Disposable.call(this);

  /**
   * Maximum number of objects allowed
   * @type {number}
   * @private
   */
  this.maxCount_ = maxCount;

  /**
   * Queue used to store objects that are currently in the pool and available
   * to be used.
   * @type {Array}
   * @private
   */
  this.freeQueue_ = [];

  this.createInitial_(initialCount);
};
goog.inherits(goog.structs.SimplePool, goog.Disposable);


/**
 * Function for overriding createObject. The avoids a common case requiring
 * subclassing this class.
 * @type {Function}
 * @private
 */
goog.structs.SimplePool.prototype.createObjectFn_ = null;


/**
 * Function for overriding disposeObject. The avoids a common case requiring
 * subclassing this class.
 * @type {Function}
 * @private
 */
goog.structs.SimplePool.prototype.disposeObjectFn_ = null;


/**
 * Sets the {@code createObject} function which is used for creating a new
 * object in the pool.
 * @param {Function} createObjectFn Create object function which returns the
 *     newly createrd object.
 */
goog.structs.SimplePool.prototype.setCreateObjectFn = function(createObjectFn) {
  this.createObjectFn_ = createObjectFn;
};


/**
 * Sets the {@code disposeObject} function which is used for disposing of an
 * object in the pool.
 * @param {Function} disposeObjectFn Dispose object function which takes the
 *     object to dispose as a parameter.
 */
goog.structs.SimplePool.prototype.setDisposeObjectFn = function(
    disposeObjectFn) {
  this.disposeObjectFn_ = disposeObjectFn;
};


/**
 * Gets an unused object from the the pool, if there is one available,
 * otherwise creates a new one.
 * @return {*} An object from the pool or a new one if necessary.
 */
goog.structs.SimplePool.prototype.getObject = function() {
  if (this.freeQueue_.length) {
    return this.freeQueue_.pop();
  }
  return this.createObject();
};


/**
 * Returns an object to the pool so that it can be reused. If the pool is
 * already full, the object is disposed instead.
 * @param {*} obj The object to release.
 */
goog.structs.SimplePool.prototype.releaseObject = function(obj) {
  if (this.freeQueue_.length < this.maxCount_) {
    this.freeQueue_.push(obj);
  } else {
    this.disposeObject(obj);
  }
};


/**
 * Populates the pool with initialCount objects.
 * @param {number} initialCount The number of objects to add to the pool.
 * @private
 */
goog.structs.SimplePool.prototype.createInitial_ = function(initialCount) {
  if (initialCount > this.maxCount_) {
    throw Error('[goog.structs.SimplePool] Initial cannot be greater than max');
  }
  for (var i = 0; i < initialCount; i++) {
    this.freeQueue_.push(this.createObject());
  }
};


/**
 * Should be overriden by sub-classes to return an instance of the object type
 * that is expected in the pool.
 * @return {*} The created object.
 */
goog.structs.SimplePool.prototype.createObject = function() {
  if (this.createObjectFn_) {
    return this.createObjectFn_();
  } else {
    return {};
  }
};


/**
 * Should be overriden to dispose of an object. Default implementation is to
 * remove all of the object's members, which should render it useless. Calls the
 *  object's dispose method, if available.
 * @param {*} obj The object to dispose.
 */
goog.structs.SimplePool.prototype.disposeObject = function(obj) {
  if (this.disposeObjectFn_) {
    this.disposeObjectFn_(obj);
  } else if (goog.isObject(obj)) {
    if (goog.isFunction(obj.dispose)) {
      obj.dispose();
    } else {
      for (var i in obj) {
        delete obj[i];
      }
    }
  }
};


/**
 * Disposes of the pool and all objects currently held in the pool.
 * @override
 * @protected
 */
goog.structs.SimplePool.prototype.disposeInternal = function() {
  goog.structs.SimplePool.superClass_.disposeInternal.call(this);
  // Call disposeObject on each object held by the pool.
  var freeQueue = this.freeQueue_;
  while (freeQueue.length) {
    this.disposeObject(freeQueue.pop());
  }
  delete this.freeQueue_;
};

// Input 126
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the Tracer class and associated classes.
 *
 * @see ../demos/tracer.html
 */

goog.provide('goog.debug.Trace');

goog.require('goog.array');
goog.require('goog.debug.Logger');
goog.require('goog.iter');
goog.require('goog.structs.Map');
goog.require('goog.structs.SimplePool');



/**
 * Class used for singleton goog.debug.Trace.  Used for timing slow points in
 * the code. Based on the java Tracer class but optimized for javascript.
 * See com.google.common.base.Tracer.
 * @constructor
 * @private
 */
goog.debug.Trace_ = function() {

  /**
   * Events in order.
   * @type {Array.<goog.debug.Trace_.Event_>}
   * @private
   */
  this.events_ = [];

  /**
   * Outstanding events that have started but haven't yet ended. The keys are
   * numeric ids and the values are goog.debug.Trace_.Event_ objects.
   * @type {goog.structs.Map}
   * @private
   */
  this.outstandingEvents_ = new goog.structs.Map();

  /**
   * Start time of the event trace
   * @type {number}
   * @private
   */
  this.startTime_ = 0;

  /**
   * Cummulative overhead of calls to startTracer
   * @type {number}
   * @private
   */
  this.tracerOverheadStart_ = 0;

  /**
   * Cummulative overhead of calls to endTracer
   * @type {number}
   * @private
   */
  this.tracerOverheadEnd_ = 0;

  /**
   * Cummulative overhead of calls to addComment
   * @type {number}
   * @private
   */
  this.tracerOverheadComment_ = 0;

  /**
   * Keeps stats on different types of tracers. The keys are strings and the
   * values are goog.debug.Stat
   * @type {goog.structs.Map}
   * @private
   */
  this.stats_ = new goog.structs.Map();

  /**
   * Total number of traces created in the trace.
   * @type {number}
   * @private
   */
  this.tracerCount_ = 0;

  /**
   * Total number of comments created in the trace.
   * @type {number}
   * @private
   */
  this.commentCount_ = 0;

  /**
   * Next id to use for the trace.
   * @type {number}
   * @private
   */
  this.nextId_ = 1;

  /**
   * A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
   * garbage collecting these (which is very expensive in IE6).
   * @type {goog.structs.SimplePool}
   * @private
   */
  this.eventPool_ = new goog.structs.SimplePool(0, 4000);
  this.eventPool_.createObject = function() {
    return new goog.debug.Trace_.Event_();
  };


  /**
   * A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
   * garbage collecting these (which is very expensive in IE6).
   * @type {goog.structs.SimplePool}
   * @private
   */
  this.statPool_ = new goog.structs.SimplePool(0, 50);
  this.statPool_.createObject = function() {
    return new goog.debug.Trace_.Stat_();
  };

  var that = this;
  this.idPool_ = new goog.structs.SimplePool(0, 2000);

  // TODO(nicksantos): SimplePool is supposed to only return objects.
  // Reconcile this so that we don't have to cast to number below.
  this.idPool_.createObject = function() {
    return String(that.nextId_++);
  };
  this.idPool_.disposeObject = function(obj) {};

  /**
   * Default threshold below which a tracer shouldn't be reported
   * @type {number}
   * @private
   */
  this.defaultThreshold_ = 3;
};


/**
 * Logger for the tracer
 * @type {goog.debug.Logger}
 * @private
 */
goog.debug.Trace_.prototype.logger_ =
    goog.debug.Logger.getLogger('goog.debug.Trace');


/**
 * Maximum size of the trace before we discard events
 * @type {number}
 */
goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;


/**
 * Event type supported by tracer
 * @enum {number}
 */
goog.debug.Trace_.EventType = {
  /**
   * Start event type
   */
  START: 0,

  /**
   * Stop event type
   */
  STOP: 1,

  /**
   * Comment event type
   */
  COMMENT: 2
};



/**
 * Class to keep track of a stat of a single tracer type. Stores the count
 * and cumulative time.
 * @constructor
 * @private
 */
goog.debug.Trace_.Stat_ = function() {
  /**
   * Number of tracers
   * @type {number}
   */
  this.count = 0;

  /**
   * Cumulative time of traces
   * @type {number}
   */
  this.time = 0;

  /**
   * Total number of allocations for this tracer type
   * @type {number}
   */
  this.varAlloc = 0;
};


/**
 * @type {string|null|undefined}
 */
goog.debug.Trace_.Stat_.prototype.type;


/**
 * @return {string} A string describing the tracer stat.
 * @override
 */
goog.debug.Trace_.Stat_.prototype.toString = function() {
  var sb = [];
  sb.push(this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
      ' ms)');
  if (this.varAlloc) {
    sb.push(' [VarAlloc = ', this.varAlloc, ']');
  }
  return sb.join('');
};



/**
 * Private class used to encapsulate a single event, either the start or stop
 * of a tracer.
 * @constructor
 * @private
 */
goog.debug.Trace_.Event_ = function() {
  // the fields are different for different events - see usage in code
};


/**
 * @type {string|null|undefined}
 */
goog.debug.Trace_.Event_.prototype.type;


/**
 * Returns a formatted string for the event.
 * @param {number} startTime The start time of the trace to generate relative
 * times.
 * @param {number} prevTime The completion time of the previous event or -1.
 * @param {string} indent Extra indent for the message
 *     if there was no previous event.
 * @return {string} The formatted tracer string.
 */
goog.debug.Trace_.Event_.prototype.toTraceString = function(startTime, prevTime,
    indent) {
  var sb = [];

  if (prevTime == -1) {
    sb.push('    ');
  } else {
    sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
  }

  sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
  if (this.eventType == goog.debug.Trace_.EventType.START) {
    sb.push(' Start        ');
  } else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
    sb.push(' Done ');
    var delta = this.stopTime - this.startTime;
    sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
  } else {
    sb.push(' Comment      ');
  }

  sb.push(indent, this);
  if (this.totalVarAlloc > 0) {
    sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
  }
  return sb.join('');
};


/**
 * @return {string} A string describing the tracer event.
 * @override
 */
goog.debug.Trace_.Event_.prototype.toString = function() {
  if (this.type == null) {
    return this.comment;
  } else {
    return '[' + this.type + '] ' + this.comment;
  }
};


/**
 * Add the ability to explicitly set the start time. This is useful for example
 * for measuring initial load time where you can set a variable as soon as the
 * main page of the app is loaded and then later call this function when the
 * Tracer code has been loaded.
 * @param {number} startTime The start time to set.
 */
goog.debug.Trace_.prototype.setStartTime = function(startTime) {
  this.startTime_ = startTime;
};


/**
 * Initializes and resets the current trace
 * @param {number} defaultThreshold The default threshold below which the
 * tracer output will be supressed. Can be overridden on a per-Tracer basis.
 */
goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
  this.reset(defaultThreshold);
};


/**
 * Clears the current trace
 */
goog.debug.Trace_.prototype.clearCurrentTrace = function() {
  this.reset(0);
};


/**
 * Resets the trace.
 * @param {number} defaultThreshold The default threshold below which the
 * tracer output will be supressed. Can be overridden on a per-Tracer basis.
 */
goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
  this.defaultThreshold_ = defaultThreshold;

  for (var i = 0; i < this.events_.length; i++) {
    var id = /** @type {Object} */ (this.eventPool_).id;
    if (id) {
      this.idPool_.releaseObject(id);
    }
    this.eventPool_.releaseObject(this.events_[i]);
  }

  this.events_.length = 0;
  this.outstandingEvents_.clear();
  this.startTime_ = goog.debug.Trace_.now();
  this.tracerOverheadStart_ = 0;
  this.tracerOverheadEnd_ = 0;
  this.tracerOverheadComment_ = 0;
  this.tracerCount_ = 0;
  this.commentCount_ = 0;

  var keys = this.stats_.getKeys();
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var stat = this.stats_.get(key);
    stat.count = 0;
    stat.time = 0;
    stat.varAlloc = 0;
    this.statPool_.releaseObject(/** @type {Object} */ (stat));
  }
  this.stats_.clear();
};


/**
 * Starts a tracer
 * @param {string} comment A comment used to identify the tracer. Does not
 *     need to be unique.
 * @param {string=} opt_type Type used to identify the tracer. If a Trace is
 *     given a type (the first argument to the constructor) and multiple Traces
 *     are done on that type then a "TOTAL line will be produced showing the
 *     total number of traces and the sum of the time
 *     ("TOTAL Database 2 (37 ms)" in our example). These traces should be
 *     mutually exclusive or else the sum won't make sense (the time will
 *     be double counted if the second starts before the first ends).
 * @return {number} The identifier for the tracer that should be passed to the
 *     the stopTracer method.
 */
goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
  var tracerStartTime = goog.debug.Trace_.now();
  var varAlloc = this.getTotalVarAlloc();
  var outstandingEventCount = this.outstandingEvents_.getCount();
  if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
    this.logger_.warning('Giant thread trace. Clearing to ' +
                         'avoid memory leak.');
    // This is the more likely case. This usually means that we
    // either forgot to clear the trace or else we are performing a
    // very large number of events
    if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
      for (var i = 0; i < this.events_.length; i++) {
        var event = this.events_[i];
        if (event.id) {
          this.idPool_.releaseObject(event.id);
        }
        this.eventPool_.releaseObject(event);
      }
      this.events_.length = 0;
    }

    // This is less likely and probably indicates that a lot of traces
    // aren't being closed. We want to avoid unnecessarily clearing
    // this though in case the events do eventually finish.
    if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
      this.outstandingEvents_.clear();
    }
  }

  goog.debug.Logger.logToProfilers('Start : ' + comment);

  var event = /** @type {goog.debug.Trace_.Event_} */ (
      this.eventPool_.getObject());
  event.totalVarAlloc = varAlloc;
  event.eventType = goog.debug.Trace_.EventType.START;
  event.id = Number(this.idPool_.getObject());
  event.comment = comment;
  event.type = opt_type;
  this.events_.push(event);
  this.outstandingEvents_.set(String(event.id), event);
  this.tracerCount_++;
  var now = goog.debug.Trace_.now();
  event.startTime = event.eventTime = now;
  this.tracerOverheadStart_ += now - tracerStartTime;
  return event.id;
};


/**
 * Stops a tracer
 * @param {number|undefined|null} id The id of the tracer that is ending.
 * @param {number=} opt_silenceThreshold Threshold below which the tracer is
 *    silenced.
 * @return {?number} The elapsed time for the tracer or null if the tracer
 *    identitifer was not recognized.
 */
goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
  // this used to call goog.isDef(opt_silenceThreshold) but that causes an
  // object allocation in IE for some reason (doh!). The following code doesn't
  // cause an allocation
  var now = goog.debug.Trace_.now();
  var silenceThreshold;
  if (opt_silenceThreshold === 0) {
    silenceThreshold = 0;
  } else if (opt_silenceThreshold) {
    silenceThreshold = opt_silenceThreshold;
  } else {
    silenceThreshold = this.defaultThreshold_;
  }

  var startEvent = this.outstandingEvents_.get(String(id));
  if (startEvent == null) {
    return null;
  }

  this.outstandingEvents_.remove(String(id));

  var stopEvent;
  var elapsed = now - startEvent.startTime;
  if (elapsed < silenceThreshold) {
    var count = this.events_.length;
    for (var i = count - 1; i >= 0; i--) {
      var nextEvent = this.events_[i];
      if (nextEvent == startEvent) {
        this.events_.splice(i, 1);
        this.idPool_.releaseObject(startEvent.id);
        this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
        break;
      }
    }

  } else {
    stopEvent = /** @type {goog.debug.Trace_.Event_} */ (
        this.eventPool_.getObject());
    stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
    stopEvent.startTime = startEvent.startTime;
    stopEvent.comment = startEvent.comment;
    stopEvent.type = startEvent.type;
    stopEvent.stopTime = stopEvent.eventTime = now;

    this.events_.push(stopEvent);
  }

  var type = startEvent.type;
  var stat = null;
  if (type) {
    stat = this.getStat_(type);
    stat.count++;
    stat.time += elapsed;
  }
  if (stopEvent) {
    goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);

    stopEvent.totalVarAlloc = this.getTotalVarAlloc();

    if (stat) {
      stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
    }
  }
  var tracerFinishTime = goog.debug.Trace_.now();
  this.tracerOverheadEnd_ += tracerFinishTime - now;
  return elapsed;
};


/**
 * Sets the ActiveX object that can be used to get GC tracing in IE6.
 * @param {Object} gcTracer GCTracer ActiveX object.
 */
goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
  this.gcTracer_ = gcTracer;
};


/**
 * Returns the total number of allocations since the GC stats were reset. Only
 * works in IE.
 * @return {number} The number of allocaitons or -1 if not supported.
 */
goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
  var gcTracer = this.gcTracer_;
  // isTracing is defined on the ActiveX object.
  if (gcTracer && gcTracer['isTracing']()) {
    return gcTracer['totalVarAlloc'];
  }
  return -1;
};


/**
 * Adds a comment to the trace. Makes it possible to see when a specific event
 * happened in relation to the traces.
 * @param {string} comment A comment that is inserted into the trace.
 * @param {?string=} opt_type Type used to identify the tracer. If a comment is
 *     given a type and multiple comments are done on that type then a "TOTAL
 *     line will be produced showing the total number of comments of that type.
 * @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
 *    specified, the current time wil be used.
 */
goog.debug.Trace_.prototype.addComment = function(comment, opt_type,
                                                  opt_timeStamp) {
  var now = goog.debug.Trace_.now();
  var timeStamp = opt_timeStamp ? opt_timeStamp : now;

  var eventComment = /** @type {goog.debug.Trace_.Event_} */ (
      this.eventPool_.getObject());
  eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
  eventComment.eventTime = timeStamp;
  eventComment.type = opt_type;
  eventComment.comment = comment;
  eventComment.totalVarAlloc = this.getTotalVarAlloc();
  this.commentCount_++;

  if (opt_timeStamp) {
    var numEvents = this.events_.length;
    for (var i = 0; i < numEvents; i++) {
      var event = this.events_[i];
      var eventTime = event.eventTime;

      if (eventTime > timeStamp) {
        goog.array.insertAt(this.events_, eventComment, i);
        break;
      }
    }
    if (i == numEvents) {
      this.events_.push(eventComment);
    }
  } else {
    this.events_.push(eventComment);
  }

  var type = eventComment.type;
  if (type) {
    var stat = this.getStat_(type);
    stat.count++;
  }

  this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
};


/**
 * Gets a stat object for a particular type. The stat object is created if it
 * hasn't yet been.
 * @param {string} type The type of stat.
 * @return {goog.debug.Trace_.Stat_} The stat object.
 * @private
 */
goog.debug.Trace_.prototype.getStat_ = function(type) {
  var stat = this.stats_.get(type);
  if (!stat) {
    stat = /** @type {goog.debug.Trace_.Event_} */ (
        this.statPool_.getObject());
    stat.type = type;
    this.stats_.set(type, stat);
  }
  return /** @type {goog.debug.Trace_.Stat_} */(stat);
};


/**
 * Returns a formatted string for the current trace
 * @return {string} A formatted string that shows the timings of the current
 *     trace.
 */
goog.debug.Trace_.prototype.getFormattedTrace = function() {
  return this.toString();
};


/**
 * Returns a formatted string that describes the thread trace.
 * @return {string} A formatted string.
 * @override
 */
goog.debug.Trace_.prototype.toString = function() {
  var sb = [];
  var etime = -1;
  var indent = [];
  for (var i = 0; i < this.events_.length; i++) {
    var e = this.events_[i];
    if (e.eventType == goog.debug.Trace_.EventType.STOP) {
      indent.pop();
    }
    sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
    etime = e.eventTime;
    sb.push('\n');
    if (e.eventType == goog.debug.Trace_.EventType.START) {
      indent.push('|  ');
    }
  }

  if (this.outstandingEvents_.getCount() != 0) {
    var now = goog.debug.Trace_.now();

    sb.push(' Unstopped timers:\n');
    goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
      sb.push('  ', startEvent, ' (', now - startEvent.startTime,
          ' ms, started at ',
          goog.debug.Trace_.formatTime_(startEvent.startTime),
          ')\n');
    });
  }

  var statKeys = this.stats_.getKeys();
  for (var i = 0; i < statKeys.length; i++) {
    var stat = this.stats_.get(statKeys[i]);
    if (stat.count > 1) {
      sb.push(' TOTAL ', stat, '\n');
    }
  }

  sb.push('Total tracers created ', this.tracerCount_, '\n',
      'Total comments created ', this.commentCount_, '\n',
      'Overhead start: ', this.tracerOverheadStart_, ' ms\n',
      'Overhead end: ', this.tracerOverheadEnd_, ' ms\n',
      'Overhead comment: ', this.tracerOverheadComment_, ' ms\n');

  return sb.join('');
};


/**
 * Converts 'v' to a string and pads it with up to 3 spaces for
 * improved alignment. TODO there must be a better way
 * @param {number} v A number.
 * @return {string} A padded string.
 * @private
 */
goog.debug.Trace_.longToPaddedString_ = function(v) {
  v = Math.round(v);
  // todo (pupius) - there should be a generic string in goog.string for this
  var space = '';
  if (v < 1000) space = ' ';
  if (v < 100) space = '  ';
  if (v < 10) space = '   ';
  return space + v;
};


/**
 * Return the sec.ms part of time (if time = "20:06:11.566",  "11.566
 * @param {number} time The time in MS.
 * @return {string} A formatted string as sec.ms'.
 * @private
 */
goog.debug.Trace_.formatTime_ = function(time) {
  time = Math.round(time);
  var sec = (time / 1000) % 60;
  var ms = time % 1000;

  // TODO their must be a nicer way to get zero padded integers
  return String(100 + sec).substring(1, 3) + '.' +
         String(1000 + ms).substring(1, 4);
};


/**
 * Returns the current time. Done through a wrapper function so it can be
 * overridden by application code. Gmail has an ActiveX extension that provides
 * higher precision timing info.
 * @return {number} The current time in milliseconds.
 */
goog.debug.Trace_.now = function() {
  return goog.now();
};


/**
 * Singleton trace object
 * @type {goog.debug.Trace_}
 */
goog.debug.Trace = new goog.debug.Trace_();

// Input 127
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Error handling utilities.
 *
 */

goog.provide('goog.debug.ErrorHandler');
goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');

goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.EntryPointMonitor');
goog.require('goog.debug.Trace');



/**
 * The ErrorHandler can be used to to wrap functions with a try/catch
 * statement. If an exception is thrown, the given error handler function will
 * be called.
 *
 * When this object is disposed, it will stop handling exceptions and tracing.
 * It will also try to restore window.setTimeout and window.setInterval
 * if it wrapped them. Notice that in the general case, it is not technically
 * possible to remove the wrapper, because functions have no knowledge of
 * what they have been assigned to. So the app is responsible for other
 * forms of unwrapping.
 *
 * @param {Function} handler Handler for exceptions.
 * @constructor
 * @extends {goog.Disposable}
 * @implements {goog.debug.EntryPointMonitor}
 */
goog.debug.ErrorHandler = function(handler) {
  goog.base(this);

  /**
   * Handler for exceptions, which can do logging, reporting, etc.
   * @type {Function}
   * @private
   */
  this.errorHandlerFn_ = handler;

  /**
   * Whether errors should be wrapped in
   * goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.
   * @type {boolean}
   * @private
   */
  this.wrapErrors_ = true;  // TODO(user) Change default.

  /**
   * Whether to add a prefix to all error messages. The prefix is
   * goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option
   * only has an effect if this.wrapErrors_  is set to false.
   * @type {boolean}
   * @private
   */
  this.prefixErrorMessages_ = false;
};
goog.inherits(goog.debug.ErrorHandler, goog.Disposable);


/**
 * Whether to add tracers when instrumenting entry points.
 * @type {boolean}
 * @private
 */
goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;


/**
 * Enable tracers when instrumenting entry points.
 * @param {boolean} newVal See above.
 */
goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions =
    function(newVal) {
  this.addTracersToProtectedFunctions_ = newVal;
};


/** @override */
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
  return this.protectEntryPoint(goog.asserts.assertFunction(fn));
};


/** @override */
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
  goog.asserts.assertFunction(fn);
  return fn[this.getFunctionIndex_(false)] || fn;
};


/**
 * Private helper function to return a span that can be clicked on to display
 * an alert with the current stack trace. Newlines are replaced with a
 * placeholder so that they will not be html-escaped.
 * @param {string} stackTrace The stack trace to create a span for.
 * @return {string} A span which can be clicked on to show the stack trace.
 * @private
 */
goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {
  var buffer = [];
  buffer.push('##PE_STACK_START##');
  buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));
  buffer.push('##PE_STACK_END##');
  return buffer.join('');
};


/**
 * Get the index for a function. Used for internal indexing.
 * @param {boolean} wrapper True for the wrapper; false for the wrapped.
 * @return {string} The index where we should store the function in its
 *     wrapper/wrapped function.
 * @private
 */
goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
  return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
};


/**
 * Installs exception protection for an entry point function. When an exception
 * is thrown from a protected function, a handler will be invoked to handle it.
 *
 * @param {Function} fn An entry point function to be protected.
 * @return {!Function} A protected wrapper function that calls the entry point
 *     function.
 */
goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
  var protectedFnName = this.getFunctionIndex_(true);
  if (!fn[protectedFnName]) {
    var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
    wrapper[this.getFunctionIndex_(false)] = fn;
  }
  return fn[protectedFnName];
};


/**
 * Helps {@link #protectEntryPoint} by actually creating the protected
 * wrapper function, after {@link #protectEntryPoint} determines that one does
 * not already exist for the given function.  Can be overriden by subclasses
 * that may want to implement different error handling, or add additional
 * entry point hooks.
 * @param {!Function} fn An entry point function to be protected.
 * @return {!Function} protected wrapper function.
 * @protected
 */
goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
  var that = this;
  var tracers = this.addTracersToProtectedFunctions_;
  if (tracers) {
    var stackTrace = goog.debug.getStacktraceSimple(15);
  }
  var googDebugErrorHandlerProtectedFunction = function() {
    if (that.isDisposed()) {
      return fn.apply(this, arguments);
    }

    if (tracers) {
      var tracer = goog.debug.Trace.startTracer('protectedEntryPoint: ' +
          that.getStackTraceHolder_(stackTrace));
    }
    try {
      return fn.apply(this, arguments);
    } catch (e) {
      that.errorHandlerFn_(e);
      if (!that.wrapErrors_) {
        // Add the prefix to the existing message.
        if (that.prefixErrorMessages_) {
          if (typeof e === 'object') {
            e.message =
                goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
                e.message;
          } else {
            e = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
                e;
          }
        }
        if (goog.DEBUG) {
          // Work around for https://code.google.com/p/v8/issues/detail?id=2625
          // and https://code.google.com/p/chromium/issues/detail?id=237059
          // Custom errors and errors with custom stack traces show the wrong
          // stack trace
          // If it has a stack and Error.captureStackTrace is supported (only
          // supported in V8 as of May 2013) log the stack to the console.
          if (e && e.stack && Error.captureStackTrace &&
              goog.global['console']) {
            goog.global['console']['error'](e.message, e.stack);
          }
        }
        // Re-throw original error. This is great for debugging as it makes
        // browser JS dev consoles show the correct error and stack trace.
        throw e;
      }
      // Re-throw it since this may be expected by the caller.
      throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
    } finally {
      if (tracers) {
        goog.debug.Trace.stopTracer(tracer);
      }
    }
  };
  googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
  return googDebugErrorHandlerProtectedFunction;
};


/**
 * Installs exception protection for window.setTimeout to handle exceptions.
 */
goog.debug.ErrorHandler.prototype.protectWindowSetTimeout =
    function() {
  this.protectWindowFunctionsHelper_('setTimeout');
};


/**
 * Install exception protection for window.setInterval to handle exceptions.
 */
goog.debug.ErrorHandler.prototype.protectWindowSetInterval =
    function() {
  this.protectWindowFunctionsHelper_('setInterval');
};


/**
 * Helper function for protecting setTimeout/setInterval.
 * @param {string} fnName The name of the function we're protecting. Must
 *     be setTimeout or setInterval.
 * @private
 */
goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ =
    function(fnName) {
  var win = goog.getObjectByName('window');
  var originalFn = win[fnName];
  var that = this;
  win[fnName] = function(fn, time) {
    // Don't try to protect strings. In theory, we could try to globalEval
    // the string, but this seems to lead to permission errors on IE6.
    if (goog.isString(fn)) {
      fn = goog.partial(goog.globalEval, fn);
    }
    fn = that.protectEntryPoint(fn);

    // IE doesn't support .call for setInterval/setTimeout, but it
    // also doesn't care what "this" is, so we can just call the
    // original function directly
    if (originalFn.call) {
      return originalFn.call(this, fn, time);
    } else {
      return originalFn(fn, time);
    }
  };
  win[fnName][this.getFunctionIndex_(false)] = originalFn;
};


/**
 * Set whether to wrap errors that occur in protected functions in a
 * goog.debug.ErrorHandler.ProtectedFunctionError.
 * @param {boolean} wrapErrors Whether to wrap errors.
 */
goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {
  this.wrapErrors_ = wrapErrors;
};


/**
 * Set whether to add a prefix to all error messages that occur in protected
 * functions.
 * @param {boolean} prefixErrorMessages Whether to add a prefix to error
 *     messages.
 */
goog.debug.ErrorHandler.prototype.setPrefixErrorMessages =
    function(prefixErrorMessages) {
  this.prefixErrorMessages_ = prefixErrorMessages;
};


/** @override */
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
  // Try to unwrap window.setTimeout and window.setInterval.
  var win = goog.getObjectByName('window');
  win.setTimeout = this.unwrap(win.setTimeout);
  win.setInterval = this.unwrap(win.setInterval);

  goog.base(this, 'disposeInternal');
};



/**
 * Error thrown to the caller of a protected entry point if the entry point
 * throws an error.
 * @param {*} cause The error thrown by the entry point.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
  var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
      (cause && cause.message ? String(cause.message) : String(cause));
  goog.base(this, message);

  /**
   * The error thrown by the entry point.
   * @type {*}
   */
  this.cause = cause;

  var stack = cause && cause.stack;
  if (stack && goog.isString(stack)) {
    this.stack = /** @type {string} */ (stack);
  }
};
goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);


/**
 * Text to prefix the message with.
 * @type {string}
 */
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
    'Error in protected function: ';

// Input 128
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Browser capability checks for the events package.
 *
 */


goog.provide('goog.events.BrowserFeature');

goog.require('goog.userAgent');


/**
 * Enum of browser capabilities.
 * @enum {boolean}
 */
goog.events.BrowserFeature = {
  /**
   * Whether the button attribute of the event is W3C compliant.  False in
   * Internet Explorer prior to version 9; document-version dependent.
   */
  HAS_W3C_BUTTON: !goog.userAgent.IE ||
      goog.userAgent.isDocumentModeOrHigher(9),

  /**
   * Whether the browser supports full W3C event model.
   */
  HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
      goog.userAgent.isDocumentModeOrHigher(9),

  /**
   * To prevent default in IE7-8 for certain keydown events we need set the
   * keyCode to -1.
   */
  SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
      !goog.userAgent.isVersionOrHigher('9'),

  /**
   * Whether the {@code navigator.onLine} property is supported.
   */
  HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
      goog.userAgent.isVersionOrHigher('528'),

  /**
   * Whether HTML5 network online/offline events are supported.
   */
  HAS_HTML5_NETWORK_EVENT_SUPPORT:
      goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
      goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
      goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
      goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),

  /**
   * Whether HTML5 network events fire on document.body, or otherwise the
   * window.
   */
  HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
      goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
      goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),

  /**
   * Whether touch is enabled in the browser.
   */
  TOUCH_ENABLED:
      ('ontouchstart' in goog.global ||
          !!(goog.global['document'] &&
             document.documentElement &&
             'ontouchstart' in document.documentElement) ||
          // IE10 uses non-standard touch events, so it has a different check.
          !!(goog.global['navigator'] &&
              goog.global['navigator']['msMaxTouchPoints']))
};

// Input 129
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Event Types.
 *
 * @author arv@google.com (Erik Arvidsson)
 * @author mirkov@google.com (Mirko Visontai)
 */


goog.provide('goog.events.EventType');

goog.require('goog.userAgent');


/**
 * Constants for event names.
 * @enum {string}
 */
goog.events.EventType = {
  // Mouse events
  CLICK: 'click',
  DBLCLICK: 'dblclick',
  MOUSEDOWN: 'mousedown',
  MOUSEUP: 'mouseup',
  MOUSEOVER: 'mouseover',
  MOUSEOUT: 'mouseout',
  MOUSEMOVE: 'mousemove',
  SELECTSTART: 'selectstart', // IE, Safari, Chrome

  // Key events
  KEYPRESS: 'keypress',
  KEYDOWN: 'keydown',
  KEYUP: 'keyup',

  // Focus
  BLUR: 'blur',
  FOCUS: 'focus',
  DEACTIVATE: 'deactivate', // IE only
  // NOTE: The following two events are not stable in cross-browser usage.
  //     WebKit and Opera implement DOMFocusIn/Out.
  //     IE implements focusin/out.
  //     Gecko implements neither see bug at
  //     https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
  // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
  //     http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
  // You can use FOCUS in Capture phase until implementations converge.
  FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
  FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',

  // Forms
  CHANGE: 'change',
  SELECT: 'select',
  SUBMIT: 'submit',
  INPUT: 'input',
  PROPERTYCHANGE: 'propertychange', // IE only

  // Drag and drop
  DRAGSTART: 'dragstart',
  DRAG: 'drag',
  DRAGENTER: 'dragenter',
  DRAGOVER: 'dragover',
  DRAGLEAVE: 'dragleave',
  DROP: 'drop',
  DRAGEND: 'dragend',

  // WebKit touch events.
  TOUCHSTART: 'touchstart',
  TOUCHMOVE: 'touchmove',
  TOUCHEND: 'touchend',
  TOUCHCANCEL: 'touchcancel',

  // Misc
  BEFOREUNLOAD: 'beforeunload',
  CONTEXTMENU: 'contextmenu',
  DOMCONTENTLOADED: 'DOMContentLoaded',
  ERROR: 'error',
  HELP: 'help',
  LOAD: 'load',
  LOSECAPTURE: 'losecapture',
  READYSTATECHANGE: 'readystatechange',
  RESIZE: 'resize',
  SCROLL: 'scroll',
  UNLOAD: 'unload',

  // HTML 5 History events
  // See http://www.w3.org/TR/html5/history.html#event-definitions
  HASHCHANGE: 'hashchange',
  PAGEHIDE: 'pagehide',
  PAGESHOW: 'pageshow',
  POPSTATE: 'popstate',

  // Copy and Paste
  // Support is limited. Make sure it works on your favorite browser
  // before using.
  // http://www.quirksmode.org/dom/events/cutcopypaste.html
  COPY: 'copy',
  PASTE: 'paste',
  CUT: 'cut',
  BEFORECOPY: 'beforecopy',
  BEFORECUT: 'beforecut',
  BEFOREPASTE: 'beforepaste',

  // HTML5 online/offline events.
  // http://www.w3.org/TR/offline-webapps/#related
  ONLINE: 'online',
  OFFLINE: 'offline',

  // HTML 5 worker events
  MESSAGE: 'message',
  CONNECT: 'connect',

  // CSS transition events. Based on the browser support described at:
  // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
  TRANSITIONEND: goog.userAgent.WEBKIT ? 'webkitTransitionEnd' :
      (goog.userAgent.OPERA ? 'oTransitionEnd' : 'transitionend'),

  // IE specific events.
  // See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx
  MSGESTURECHANGE: 'MSGestureChange',
  MSGESTUREEND: 'MSGestureEnd',
  MSGESTUREHOLD: 'MSGestureHold',
  MSGESTURESTART: 'MSGestureStart',
  MSGESTURETAP: 'MSGestureTap',
  MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
  MSINERTIASTART: 'MSInertiaStart',
  MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
  MSPOINTERCANCEL: 'MSPointerCancel',
  MSPOINTERDOWN: 'MSPointerDown',
  MSPOINTERMOVE: 'MSPointerMove',
  MSPOINTEROVER: 'MSPointerOver',
  MSPOINTEROUT: 'MSPointerOut',
  MSPOINTERUP: 'MSPointerUp',

  // Native IMEs/input tools events.
  TEXTINPUT: 'textinput',
  COMPOSITIONSTART: 'compositionstart',
  COMPOSITIONUPDATE: 'compositionupdate',
  COMPOSITIONEND: 'compositionend'
};

// Input 130
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Useful compiler idioms.
 *
 */

goog.provide('goog.reflect');


/**
 * Syntax for object literal casts.
 * @see http://go/jscompiler-renaming
 * @see http://code.google.com/p/closure-compiler/wiki/
 *      ExperimentalTypeBasedPropertyRenaming
 *
 * Use this if you have an object literal whose keys need to have the same names
 * as the properties of some class even after they are renamed by the compiler.
 *
 * @param {!Function} type Type to cast to.
 * @param {Object} object Object literal to cast.
 * @return {Object} The object literal.
 */
goog.reflect.object = function(type, object) {
  return object;
};


/**
 * To assert to the compiler that an operation is needed when it would
 * otherwise be stripped. For example:
 * <code>
 *     // Force a layout
 *     goog.reflect.sinkValue(dialog.offsetHeight);
 * </code>
 * @type {!Function}
 */
goog.reflect.sinkValue = function(x) {
  goog.reflect.sinkValue[' '](x);
  return x;
};


/**
 * The compiler should optimize this function away iff no one ever uses
 * goog.reflect.sinkValue.
 */
goog.reflect.sinkValue[' '] = goog.nullFunction;


/**
 * Check if a property can be accessed without throwing an exception.
 * @param {Object} obj The owner of the property.
 * @param {string} prop The property name.
 * @return {boolean} Whether the property is accessible. Will also return true
 *     if obj is null.
 */
goog.reflect.canAccessProperty = function(obj, prop) {
  /** @preserveTry */
  try {
    goog.reflect.sinkValue(obj[prop]);
    return true;
  } catch (e) {}
  return false;
};

// Input 131
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A patched, standardized event object for browser events.
 *
 * <pre>
 * The patched event object contains the following members:
 * - type           {string}    Event type, e.g. 'click'
 * - timestamp      {Date}      A date object for when the event was fired
 * - target         {Object}    The element that actually triggered the event
 * - currentTarget  {Object}    The element the listener is attached to
 * - relatedTarget  {Object}    For mouseover and mouseout, the previous object
 * - offsetX        {number}    X-coordinate relative to target
 * - offsetY        {number}    Y-coordinate relative to target
 * - clientX        {number}    X-coordinate relative to viewport
 * - clientY        {number}    Y-coordinate relative to viewport
 * - screenX        {number}    X-coordinate relative to the edge of the screen
 * - screenY        {number}    Y-coordinate relative to the edge of the screen
 * - button         {number}    Mouse button. Use isButton() to test.
 * - keyCode        {number}    Key-code
 * - ctrlKey        {boolean}   Was ctrl key depressed
 * - altKey         {boolean}   Was alt key depressed
 * - shiftKey       {boolean}   Was shift key depressed
 * - metaKey        {boolean}   Was meta key depressed
 * - defaultPrevented {boolean} Whether the default action has been prevented
 * - state          {Object}    History state object
 *
 * NOTE: The keyCode member contains the raw browser keyCode. For normalized
 * key and character code use {@link goog.events.KeyHandler}.
 * </pre>
 *
 */

goog.provide('goog.events.BrowserEvent');
goog.provide('goog.events.BrowserEvent.MouseButton');

goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.reflect');
goog.require('goog.userAgent');



/**
 * Accepts a browser event object and creates a patched, cross browser event
 * object.
 * The content of this object will not be initialized if no event object is
 * provided. If this is the case, init() needs to be invoked separately.
 * @param {Event=} opt_e Browser event object.
 * @param {EventTarget=} opt_currentTarget Current target for event.
 * @constructor
 * @extends {goog.events.Event}
 */
goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
  if (opt_e) {
    this.init(opt_e, opt_currentTarget);
  }
};
goog.inherits(goog.events.BrowserEvent, goog.events.Event);


/**
 * Normalized button constants for the mouse.
 * @enum {number}
 */
goog.events.BrowserEvent.MouseButton = {
  LEFT: 0,
  MIDDLE: 1,
  RIGHT: 2
};


/**
 * Static data for mapping mouse buttons.
 * @type {Array.<number>}
 */
goog.events.BrowserEvent.IEButtonMap = [
  1, // LEFT
  4, // MIDDLE
  2  // RIGHT
];


/**
 * Target that fired the event.
 * @override
 * @type {Node}
 */
goog.events.BrowserEvent.prototype.target = null;


/**
 * Node that had the listener attached.
 * @override
 * @type {Node|undefined}
 */
goog.events.BrowserEvent.prototype.currentTarget;


/**
 * For mouseover and mouseout events, the related object for the event.
 * @type {Node}
 */
goog.events.BrowserEvent.prototype.relatedTarget = null;


/**
 * X-coordinate relative to target.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.offsetX = 0;


/**
 * Y-coordinate relative to target.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.offsetY = 0;


/**
 * X-coordinate relative to the window.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.clientX = 0;


/**
 * Y-coordinate relative to the window.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.clientY = 0;


/**
 * X-coordinate relative to the monitor.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.screenX = 0;


/**
 * Y-coordinate relative to the monitor.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.screenY = 0;


/**
 * Which mouse button was pressed.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.button = 0;


/**
 * Keycode of key press.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.keyCode = 0;


/**
 * Keycode of key press.
 * @type {number}
 */
goog.events.BrowserEvent.prototype.charCode = 0;


/**
 * Whether control was pressed at time of event.
 * @type {boolean}
 */
goog.events.BrowserEvent.prototype.ctrlKey = false;


/**
 * Whether alt was pressed at time of event.
 * @type {boolean}
 */
goog.events.BrowserEvent.prototype.altKey = false;


/**
 * Whether shift was pressed at time of event.
 * @type {boolean}
 */
goog.events.BrowserEvent.prototype.shiftKey = false;


/**
 * Whether the meta key was pressed at time of event.
 * @type {boolean}
 */
goog.events.BrowserEvent.prototype.metaKey = false;


/**
 * History state object, only set for PopState events where it's a copy of the
 * state object provided to pushState or replaceState.
 * @type {Object}
 */
goog.events.BrowserEvent.prototype.state;


/**
 * Whether the default platform modifier key was pressed at time of event.
 * (This is control for all platforms except Mac, where it's Meta.
 * @type {boolean}
 */
goog.events.BrowserEvent.prototype.platformModifierKey = false;


/**
 * The browser event object.
 * @type {Event}
 * @private
 */
goog.events.BrowserEvent.prototype.event_ = null;


/**
 * Accepts a browser event object and creates a patched, cross browser event
 * object.
 * @param {Event} e Browser event object.
 * @param {EventTarget=} opt_currentTarget Current target for event.
 */
goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
  var type = this.type = e.type;
  goog.events.Event.call(this, type);

  // TODO(nicksantos): Change this.target to type EventTarget.
  this.target = /** @type {Node} */ (e.target) || e.srcElement;

  // TODO(nicksantos): Change this.currentTarget to type EventTarget.
  this.currentTarget = /** @type {Node} */ (opt_currentTarget);

  var relatedTarget = /** @type {Node} */ (e.relatedTarget);
  if (relatedTarget) {
    // There's a bug in FireFox where sometimes, relatedTarget will be a
    // chrome element, and accessing any property of it will get a permission
    // denied exception. See:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=497780
    if (goog.userAgent.GECKO) {
      if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
        relatedTarget = null;
      }
    }
    // TODO(arv): Use goog.events.EventType when it has been refactored into its
    // own file.
  } else if (type == goog.events.EventType.MOUSEOVER) {
    relatedTarget = e.fromElement;
  } else if (type == goog.events.EventType.MOUSEOUT) {
    relatedTarget = e.toElement;
  }

  this.relatedTarget = relatedTarget;

  // Webkit emits a lame warning whenever layerX/layerY is accessed.
  // http://code.google.com/p/chromium/issues/detail?id=101733
  this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
      e.offsetX : e.layerX;
  this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
      e.offsetY : e.layerY;

  this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
  this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
  this.screenX = e.screenX || 0;
  this.screenY = e.screenY || 0;

  this.button = e.button;

  this.keyCode = e.keyCode || 0;
  this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
  this.ctrlKey = e.ctrlKey;
  this.altKey = e.altKey;
  this.shiftKey = e.shiftKey;
  this.metaKey = e.metaKey;
  this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
  this.state = e.state;
  this.event_ = e;
  if (e.defaultPrevented) {
    this.preventDefault();
  }
  delete this.propagationStopped_;
};


/**
 * Tests to see which button was pressed during the event. This is really only
 * useful in IE and Gecko browsers. And in IE, it's only useful for
 * mousedown/mouseup events, because click only fires for the left mouse button.
 *
 * Safari 2 only reports the left button being clicked, and uses the value '1'
 * instead of 0. Opera only reports a mousedown event for the middle button, and
 * no mouse events for the right button. Opera has default behavior for left and
 * middle click that can only be overridden via a configuration setting.
 *
 * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
 *
 * @param {goog.events.BrowserEvent.MouseButton} button The button
 *     to test for.
 * @return {boolean} True if button was pressed.
 */
goog.events.BrowserEvent.prototype.isButton = function(button) {
  if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
    if (this.type == 'click') {
      return button == goog.events.BrowserEvent.MouseButton.LEFT;
    } else {
      return !!(this.event_.button &
          goog.events.BrowserEvent.IEButtonMap[button]);
    }
  } else {
    return this.event_.button == button;
  }
};


/**
 * Whether this has an "action"-producing mouse button.
 *
 * By definition, this includes left-click on windows/linux, and left-click
 * without the ctrl key on Macs.
 *
 * @return {boolean} The result.
 */
goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
  // Webkit does not ctrl+click to be a right-click, so we
  // normalize it to behave like Gecko and Opera.
  return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
      !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
};


/**
 * @override
 */
goog.events.BrowserEvent.prototype.stopPropagation = function() {
  goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
  if (this.event_.stopPropagation) {
    this.event_.stopPropagation();
  } else {
    this.event_.cancelBubble = true;
  }
};


/**
 * @override
 */
goog.events.BrowserEvent.prototype.preventDefault = function() {
  goog.events.BrowserEvent.superClass_.preventDefault.call(this);
  var be = this.event_;
  if (!be.preventDefault) {
    be.returnValue = false;
    if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
      /** @preserveTry */
      try {
        // Most keys can be prevented using returnValue. Some special keys
        // require setting the keyCode to -1 as well:
        //
        // In IE7:
        // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
        //
        // In IE8:
        // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
        //
        // We therefore do this for all function keys as well as when Ctrl key
        // is pressed.
        var VK_F1 = 112;
        var VK_F12 = 123;
        if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
          be.keyCode = -1;
        }
      } catch (ex) {
        // IE throws an 'access denied' exception when trying to change
        // keyCode in some situations (e.g. srcElement is input[type=file],
        // or srcElement is an anchor tag rewritten by parent's innerHTML).
        // Do nothing in this case.
      }
    }
  } else {
    be.preventDefault();
  }
};


/**
 * @return {Event} The underlying browser event object.
 */
goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
  return this.event_;
};


/** @override */
goog.events.BrowserEvent.prototype.disposeInternal = function() {
};

// Input 132
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview An interface for a listenable JavaScript object.
 */

goog.provide('goog.events.Listenable');
goog.provide('goog.events.ListenableKey');



/**
 * A listenable interface. Also see goog.events.EventTarget.
 * @interface
 */
goog.events.Listenable = function() {};


/**
 * An expando property to indicate that an object implements
 * goog.events.Listenable.
 *
 * See addImplementation/isImplementedBy.
 *
 * @type {string}
 * @const
 */
goog.events.Listenable.IMPLEMENTED_BY_PROP =
    'closure_listenable_' + ((Math.random() * 1e6) | 0);


/**
 * Marks a given class (constructor) as an implementation of
 * Listenable, do that we can query that fact at runtime. The class
 * must have already implemented the interface.
 * @param {!Function} cls The class constructor. The corresponding
 *     class must have already implemented the interface.
 */
goog.events.Listenable.addImplementation = function(cls) {
  cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
};


/**
 * @param {Object} obj The object to check.
 * @return {boolean} Whether a given instance implements
 *     Listenable. The class/superclass of the instance must call
 *     addImplementation.
 */
goog.events.Listenable.isImplementedBy = function(obj) {
  return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
};


/**
 * Adds an event listener. A listener can only be added once to an
 * object and if it is added again the key for the listener is
 * returned. Note that if the existing listener is a one-off listener
 * (registered via listenOnce), it will no longer be a one-off
 * listener after a call to listen().
 *
 * @param {string} type Event type or array of event types.
 * @param {!Function} listener Callback method, or an object
 *     with a handleEvent function.
 * @param {boolean=} opt_useCapture Whether to fire in capture phase
 *     (defaults to false).
 * @param {Object=} opt_listenerScope Object in whose scope to call the
 *     listener.
 * @return {goog.events.ListenableKey} Unique key for the listener.
 */
goog.events.Listenable.prototype.listen;


/**
 * Adds an event listener that is removed automatically after the
 * listener fired once.
 *
 * If an existing listener already exists, listenOnce will do
 * nothing. In particular, if the listener was previously registered
 * via listen(), listenOnce() will not turn the listener into a
 * one-off listener. Similarly, if there is already an existing
 * one-off listener, listenOnce does not modify the listeners (it is
 * still a once listener).
 *
 * @param {string} type Event type or array of event types.
 * @param {!Function} listener Callback method, or an object
 *     with a handleEvent function.
 * @param {boolean=} opt_useCapture Whether to fire in capture phase
 *     (defaults to false).
 * @param {Object=} opt_listenerScope Object in whose scope to call the
 *     listener.
 * @return {goog.events.ListenableKey} Unique key for the listener.
 */
goog.events.Listenable.prototype.listenOnce;


/**
 * Removes an event listener which was added with listen() or listenOnce().
 *
 * Implementation needs to call goog.events.cleanUp.
 *
 * @param {string} type Event type or array of event types.
 * @param {!Function} listener Callback method, or an object
 *     with a handleEvent function. TODO(user): Consider whether
 *     we can remove Object.
 * @param {boolean=} opt_useCapture Whether to fire in capture phase
 *     (defaults to false).
 * @param {Object=} opt_listenerScope Object in whose scope to call
 *     the listener.
 * @return {boolean} Whether any listener was removed.
 */
goog.events.Listenable.prototype.unlisten;


/**
 * Removes an event listener which was added with listen() by the key
 * returned by listen().
 *
 * Implementation needs to call goog.events.cleanUp.
 *
 * @param {goog.events.ListenableKey} key The key returned by
 *     listen() or listenOnce().
 * @return {boolean} Whether any listener was removed.
 */
goog.events.Listenable.prototype.unlistenByKey;


/**
 * Dispatches an event (or event like object) and calls all listeners
 * listening for events of this type. The type of the event is decided by the
 * type property on the event object.
 *
 * If any of the listeners returns false OR calls preventDefault then this
 * function will return false.  If one of the capture listeners calls
 * stopPropagation, then the bubble listeners won't fire.
 *
 * @param {goog.events.EventLike} e Event object.
 * @return {boolean} If anyone called preventDefault on the event object (or
 *     if any of the listeners returns false) this will also return false.
 */
goog.events.Listenable.prototype.dispatchEvent;


/**
 * Removes all listeners from this listenable. If type is specified,
 * it will only remove listeners of the particular type. otherwise all
 * registered listeners will be removed.
 *
 * Implementation needs to call goog.events.cleanUp for each removed
 * listener.
 *
 * @param {string=} opt_type Type of event to remove, default is to
 *     remove all types.
 * @return {number} Number of listeners removed.
 */
goog.events.Listenable.prototype.removeAllListeners;


/**
 * Fires all registered listeners in this listenable for the given
 * type and capture mode, passing them the given eventObject. This
 * does not perform actual capture/bubble. Only implementors of the
 * interface should be using this.
 *
 * @param {string} type The type of the listeners to fire.
 * @param {boolean} capture The capture mode of the listeners to fire.
 * @param {goog.events.Event} eventObject The event object to fire.
 * @return {boolean} Whether all listeners succeeded without
 *     attempting to prevent default behavior. If any listener returns
 *     false or called goog.events.Event#preventDefault, this returns
 *     false.
 */
goog.events.Listenable.prototype.fireListeners;


/**
 * Gets all listeners in this listenable for the given type and
 * capture mode.
 *
 * @param {string} type The type of the listeners to fire.
 * @param {boolean} capture The capture mode of the listeners to fire.
 * @return {!Array.<goog.events.ListenableKey>} An array of registered
 *     listeners.
 */
goog.events.Listenable.prototype.getListeners;


/**
 * Gets the goog.events.ListenableKey for the event or null if no such
 * listener is in use.
 *
 * @param {string} type The name of the event without the 'on' prefix.
 * @param {!Function} listener The listener function to get.
 * @param {boolean} capture Whether the listener is a capturing listener.
 * @param {Object=} opt_listenerScope Object in whose scope to call the
 *     listener.
 * @return {goog.events.ListenableKey} the found listener or null if not found.
 */
goog.events.Listenable.prototype.getListener;


/**
 * Whether there is any active listeners matching the specified
 * signature. If either the type or capture parameters are
 * unspecified, the function will match on the remaining criteria.
 *
 * @param {string=} opt_type Event type.
 * @param {boolean=} opt_capture Whether to check for capture or bubble
 *     listeners.
 * @return {boolean} Whether there is any active listeners matching
 *     the requested type and/or capture phase.
 */
goog.events.Listenable.prototype.hasListener;



/**
 * An interface that describes a single registered listener.
 * @interface
 */
goog.events.ListenableKey = function() {};


/**
 * Counter used to create a unique key
 * @type {number}
 * @private
 */
goog.events.ListenableKey.counter_ = 0;


/**
 * Reserves a key to be used for ListenableKey#key field.
 * @return {number} A number to be used to fill ListenableKey#key
 *     field.
 */
goog.events.ListenableKey.reserveKey = function() {
  return ++goog.events.ListenableKey.counter_;
};


/**
 * The source event target.
 * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
 */
goog.events.ListenableKey.prototype.src;


/**
 * The event type the listener is listening to.
 * @type {string}
 */
goog.events.ListenableKey.prototype.type;


/**
 * The listener function.
 * TODO(user): Narrow the type if possible.
 * @type {Function|Object}
 */
goog.events.ListenableKey.prototype.listener;


/**
 * Whether the listener works on capture phase.
 * @type {boolean}
 */
goog.events.ListenableKey.prototype.capture;


/**
 * The 'this' object for the listener function's scope.
 * @type {Object}
 */
goog.events.ListenableKey.prototype.handler;


/**
 * A globally unique number to identify the key.
 * @type {number}
 */
goog.events.ListenableKey.prototype.key;

// Input 133
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Listener object.
 * @see ../demos/events.html
 */

goog.provide('goog.events.Listener');

goog.require('goog.events.ListenableKey');



/**
 * Simple class that stores information about a listener
 * @param {!Function} listener Callback function.
 * @param {Function} proxy Wrapper for the listener that patches the event.
 * @param {EventTarget|goog.events.Listenable} src Source object for
 *     the event.
 * @param {string} type Event type.
 * @param {boolean} capture Whether in capture or bubble phase.
 * @param {Object=} opt_handler Object in whose context to execute the callback.
 * @implements {goog.events.ListenableKey}
 * @constructor
 */
goog.events.Listener = function(
    listener, proxy, src, type, capture, opt_handler) {
  if (goog.events.Listener.ENABLE_MONITORING) {
    this.creationStack = new Error().stack;
  }

  /**
   * Callback function.
   * @type {Function}
   */
  this.listener = listener;

  /**
   * Proxy for callback that passes through {@link goog.events#HandleEvent_}.
   * @type {Function}
   */
  this.proxy = proxy;

  /**
   * Object or node that callback is listening to
   * @type {EventTarget|goog.events.Listenable}
   */
  this.src = src;

  /**
   * The event type.
   * @const {string}
   */
  this.type = type;

  /**
   * Whether the listener is being called in the capture or bubble phase
   * @const {boolean}
   */
  this.capture = !!capture;

  /**
   * Optional object whose context to execute the listener in
   * @type {Object|undefined}
   */
  this.handler = opt_handler;

  /**
   * The key of the listener.
   * @const {number}
   * @override
   */
  this.key = goog.events.ListenableKey.reserveKey();

  /**
   * Whether to remove the listener after it has been called.
   * @type {boolean}
   */
  this.callOnce = false;

  /**
   * Whether the listener has been removed.
   * @type {boolean}
   */
  this.removed = false;
};


/**
 * @define {boolean} Whether to enable the monitoring of the
 *     goog.events.Listener instances. Switching on the monitoring is only
 *     recommended for debugging because it has a significant impact on
 *     performance and memory usage. If switched off, the monitoring code
 *     compiles down to 0 bytes.
 */
goog.define('goog.events.Listener.ENABLE_MONITORING', false);


/**
 * If monitoring the goog.events.Listener instances is enabled, stores the
 * creation stack trace of the Disposable instance.
 * @type {string}
 */
goog.events.Listener.prototype.creationStack;


/**
 * Marks this listener as removed. This also remove references held by
 * this listener object (such as listener and event source).
 */
goog.events.Listener.prototype.markAsRemoved = function() {
  this.removed = true;
  this.listener = null;
  this.proxy = null;
  this.src = null;
  this.handler = null;
};

// Input 134
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Event Manager.
 *
 * Provides an abstracted interface to the browsers' event
 * systems. This uses an indirect lookup of listener functions to avoid circular
 * references between DOM (in IE) or XPCOM (in Mozilla) objects which leak
 * memory. This makes it easier to write OO Javascript/DOM code.
 *
 * It simulates capture & bubble in Internet Explorer.
 *
 * The listeners will also automagically have their event objects patched, so
 * your handlers don't need to worry about the browser.
 *
 * Example usage:
 * <pre>
 * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
 * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
 * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
 * goog.events.removeAll(myNode);
 * goog.events.removeAll();
 * </pre>
 *
 *                                            in IE and event object patching]
 *
 * @supported IE6+, FF1.5+, WebKit, Opera.
 * @see ../demos/events.html
 * @see ../demos/event-propagation.html
 * @see ../demos/stopevent.html
 */


// This uses 3 lookup tables/trees.
// listenerTree_ is a tree of type -> capture -> src uid -> [Listener]
// listeners_ is a map of key -> [Listener]
//
// The key is a field of the Listener. The Listener class also has the type,
// capture and the src so one can always trace back in the tree
//
// sources_: src uid -> [Listener]


goog.provide('goog.events');
goog.provide('goog.events.Key');
goog.provide('goog.events.ListenableType');

goog.require('goog.array');
goog.require('goog.asserts');
/** @suppress {extraRequire} */
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Listenable');
goog.require('goog.events.Listener');
goog.require('goog.object');


/**
 * @typedef {number|goog.events.ListenableKey}
 */
goog.events.Key;


/**
 * @typedef {EventTarget|goog.events.Listenable}
 */
goog.events.ListenableType;


/**
 * Container for storing event listeners and their proxies
 * @private {!Object.<goog.events.ListenableKey>}
 */
goog.events.listeners_ = {};


/**
 * The root of the listener tree
 * @private
 * @type {Object}
 */
goog.events.listenerTree_ = {};


/**
 * Lookup for mapping source UIDs to listeners.
 * @private
 * @type {Object}
 */
goog.events.sources_ = {};


/**
 * String used to prepend to IE event types.  Not a constant so that it is not
 * inlined.
 * @type {string}
 * @private
 */
goog.events.onString_ = 'on';


/**
 * Map of computed on strings for IE event types. Caching this removes an extra
 * object allocation in goog.events.listen which improves IE6 performance.
 * @type {Object}
 * @private
 */
goog.events.onStringMap_ = {};


/**
 * Separator used to split up the various parts of an event key, to help avoid
 * the possibilities of collisions.
 * @type {string}
 * @private
 */
goog.events.keySeparator_ = '_';


/**
 * Adds an event listener for a specific event on a DOM Node or an
 * object that has implemented {@link goog.events.Listenable}. A
 * listener can only be added once to an object and if it is added
 * again the key for the listener is returned. Note that if the
 * existing listener is a one-off listener (registered via
 * listenOnce), it will no longer be a one-off listener after a call
 * to listen().
 *
 * @param {goog.events.ListenableType} src The node to listen to
 *     events on.
 * @param {string|Array.<string>} type Event type or array of event types.
 * @param {Function|Object} listener Callback method, or an object with a
 *     handleEvent function.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.Key} Unique key for the listener.
 */
goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
  if (goog.isArray(type)) {
    for (var i = 0; i < type.length; i++) {
      goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
    }
    return null;
  }

  var listenableKey;
  listener = goog.events.wrapListener_(listener);
  if (goog.events.Listenable.isImplementedBy(src)) {
    listenableKey = src.listen(
        /** @type {string} */ (type), listener, opt_capt, opt_handler);
  } else {
    listenableKey = goog.events.listen_(
        /** @type {EventTarget} */ (src),
        type, listener, /* callOnce */ false, opt_capt, opt_handler);
  }

  return listenableKey;
};


/**
 * Adds an event listener for a specific event on a DOM Node or an object that
 * has implemented {@link goog.events.Listenable}. A listener can only be
 * added once to an object and if it is added again the key for the listener
 * is returned.
 *
 * Note that a one-off listener will not change an existing listener,
 * if any. On the other hand a normal listener will change existing
 * one-off listener to become a normal listener.
 *
 * @param {EventTarget} src The node to listen to events on.
 * @param {?string} type Event type or array of event types.
 * @param {!Function} listener Callback function.
 * @param {boolean} callOnce Whether the listener is a one-off
 *     listener or otherwise.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.ListenableKey} Unique key for the listener.
 * @private
 */
goog.events.listen_ = function(
    src, type, listener, callOnce, opt_capt, opt_handler) {
  if (!type) {
    throw Error('Invalid event type');
  }

  var capture = !!opt_capt;
  var map = goog.events.listenerTree_;

  if (!(type in map)) {
    map[type] = {count_: 0, remaining_: 0};
  }
  map = map[type];

  if (!(capture in map)) {
    map[capture] = {count_: 0, remaining_: 0};
    map.count_++;
  }
  map = map[capture];

  var srcUid = goog.getUid(src);
  var listenerArray, listenerObj;

  // The remaining_ property is used to be able to short circuit the iteration
  // of the event listeners.
  //
  // Increment the remaining event listeners to call even if this event might
  // already have been fired. At this point we do not know if the event has
  // been fired and it is too expensive to find out. By incrementing it we are
  // guaranteed that we will not skip any event listeners.
  map.remaining_++;

  // Do not use srcUid in map here since that will cast the number to a
  // string which will allocate one string object.
  if (!map[srcUid]) {
    listenerArray = map[srcUid] = [];
    map.count_++;
  } else {
    listenerArray = map[srcUid];
    // Ensure that the listeners do not already contain the current listener
    for (var i = 0; i < listenerArray.length; i++) {
      listenerObj = listenerArray[i];
      if (listenerObj.listener == listener &&
          listenerObj.handler == opt_handler) {

        // If this listener has been removed we should not return its key. It
        // is OK that we create new listenerObj below since the removed one
        // will be cleaned up later.
        if (listenerObj.removed) {
          break;
        }

        if (!callOnce) {
          // Ensure that, if there is an existing callOnce listener, it is no
          // longer a callOnce listener.
          listenerArray[i].callOnce = false;
        }

        // We already have this listener. Return its key.
        return listenerArray[i];
      }
    }
  }

  var proxy = goog.events.getProxy();
  listenerObj = new goog.events.Listener(
      listener, proxy, src, type, capture, opt_handler);
  listenerObj.callOnce = callOnce;

  proxy.src = src;
  proxy.listener = listenerObj;

  listenerArray.push(listenerObj);

  if (!goog.events.sources_[srcUid]) {
    goog.events.sources_[srcUid] = [];
  }
  goog.events.sources_[srcUid].push(listenerObj);

  // Attach the proxy through the browser's API
  if (src.addEventListener) {
    src.addEventListener(type, proxy, capture);
  } else {
    // The else above used to be else if (src.attachEvent) and then there was
    // another else statement that threw an exception warning the developer
    // they made a mistake. This resulted in an extra object allocation in IE6
    // due to a wrapper object that had to be implemented around the element
    // and so was removed.
    src.attachEvent(goog.events.getOnString_(type), proxy);
  }

  var key = listenerObj.key;
  goog.events.listeners_[key] = listenerObj;
  return listenerObj;
};


/**
 * Helper function for returning a proxy function.
 * @return {Function} A new or reused function object.
 */
goog.events.getProxy = function() {
  var proxyCallbackFunction = goog.events.handleBrowserEvent_;
  // Use a local var f to prevent one allocation.
  var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
      function(eventObject) {
        return proxyCallbackFunction.call(f.src, f.listener, eventObject);
      } :
      function(eventObject) {
        var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
        // NOTE(user): In IE, we hack in a capture phase. However, if
        // there is inline event handler which tries to prevent default (for
        // example <a href="..." onclick="return false">...</a>) in a
        // descendant element, the prevent default will be overridden
        // by this listener if this listener were to return true. Hence, we
        // return undefined.
        if (!v) return v;
      };
  return f;
};


/**
 * Adds an event listener for a specific event on a DomNode or an object that
 * has implemented {@link goog.events.Listenable}. After the event has fired
 * the event listener is removed from the target.
 *
 * If an existing listener already exists, listenOnce will do
 * nothing. In particular, if the listener was previously registered
 * via listen(), listenOnce() will not turn the listener into a
 * one-off listener. Similarly, if there is already an existing
 * one-off listener, listenOnce does not modify the listeners (it is
 * still a once listener).
 *
 * @param {goog.events.ListenableType} src The node to listen to
 *     events on.
 * @param {string|Array.<string>} type Event type or array of event types.
 * @param {Function|Object} listener Callback method.
 * @param {boolean=} opt_capt Fire in capture phase?.
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.Key} Unique key for the listener.
 */
goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
  if (goog.isArray(type)) {
    for (var i = 0; i < type.length; i++) {
      goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
    }
    return null;
  }

  var listenableKey;
  listener = goog.events.wrapListener_(listener);
  if (goog.events.Listenable.isImplementedBy(src)) {
    listenableKey = src.listenOnce(
        /** @type {string} */ (type), listener, opt_capt, opt_handler);
  } else {
    listenableKey = goog.events.listen_(
        /** @type {EventTarget} */ (src),
        type, listener, /* callOnce */ true, opt_capt, opt_handler);
  }

  return listenableKey;
};


/**
 * Adds an event listener with a specific event wrapper on a DOM Node or an
 * object that has implemented {@link goog.events.Listenable}. A listener can
 * only be added once to an object.
 *
 * @param {goog.events.ListenableType} src The target to listen to events on.
 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
 * @param {Function|Object} listener Callback method, or an object with a
 *     handleEvent function.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 */
goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
    opt_handler) {
  wrapper.listen(src, listener, opt_capt, opt_handler);
};


/**
 * Removes an event listener which was added with listen().
 *
 * @param {goog.events.ListenableType} src The target to stop
 *     listening to events on.
 * @param {string|Array.<string>} type The name of the event without the 'on'
 *     prefix.
 * @param {Function|Object} listener The listener function to remove.
 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase of the
 *     event.
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {?boolean} indicating whether the listener was there to remove.
 */
goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
  if (goog.isArray(type)) {
    for (var i = 0; i < type.length; i++) {
      goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
    }
    return null;
  }

  listener = goog.events.wrapListener_(listener);
  if (goog.events.Listenable.isImplementedBy(src)) {
    return src.unlisten(
        /** @type {string} */ (type), listener, opt_capt, opt_handler);
  }

  var capture = !!opt_capt;

  var listenerArray = goog.events.getListeners_(src, type, capture);
  if (!listenerArray) {
    return false;
  }

  for (var i = 0; i < listenerArray.length; i++) {
    if (listenerArray[i].listener == listener &&
        listenerArray[i].capture == capture &&
        listenerArray[i].handler == opt_handler) {
      return goog.events.unlistenByKey(listenerArray[i]);
    }
  }

  return false;
};


/**
 * Removes an event listener which was added with listen() by the key
 * returned by listen().
 *
 * @param {goog.events.Key} key The key returned by listen() for this
 *     event listener.
 * @return {boolean} indicating whether the listener was there to remove.
 */
goog.events.unlistenByKey = function(key) {
  // TODO(user): Remove this check when tests that rely on this
  // are fixed.
  if (goog.isNumber(key)) {
    return false;
  }

  var listener = /** @type {goog.events.ListenableKey} */ (key);
  if (!listener) {
    return false;
  }
  if (listener.removed) {
    return false;
  }

  var src = listener.src;
  if (goog.events.Listenable.isImplementedBy(src)) {
    return src.unlistenByKey(listener);
  }

  var type = listener.type;
  var proxy = listener.proxy;
  var capture = listener.capture;

  if (src.removeEventListener) {
    src.removeEventListener(type, proxy, capture);
  } else if (src.detachEvent) {
    src.detachEvent(goog.events.getOnString_(type), proxy);
  }

  var srcUid = goog.getUid(src);

  // In a perfect implementation we would decrement the remaining_ field here
  // but then we would need to know if the listener has already been fired or
  // not. We therefore skip doing this and in this uncommon case the entire
  // ancestor chain will need to be traversed as before.

  // Remove from sources_
  if (goog.events.sources_[srcUid]) {
    var sourcesArray = goog.events.sources_[srcUid];
    goog.array.remove(sourcesArray, listener);
    if (sourcesArray.length == 0) {
      delete goog.events.sources_[srcUid];
    }
  }

  listener.markAsRemoved();

  // There are some esoteric situations where the hash code of an object
  // can change, and we won't be able to find the listenerArray anymore.
  // For example, if you're listening on a window, and the user navigates to
  // a different window, the UID will disappear.
  //
  // It should be impossible to ever find the original listenerArray, so it
  // doesn't really matter if we can't clean it up in this case.
  var listenerArray = goog.events.listenerTree_[type][capture][srcUid];
  if (listenerArray) {
    listenerArray.needsCleanup_ = true;
    goog.events.cleanUp_(type, capture, srcUid, listenerArray);
  }

  delete goog.events.listeners_[listener.key];

  return true;
};


/**
 * Removes an event listener which was added with listenWithWrapper().
 *
 * @param {goog.events.ListenableType} src The target to stop listening to
 *     events on.
 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
 * @param {Function|Object} listener The listener function to remove.
 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase of the
 *     event.
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 */
goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
    opt_handler) {
  wrapper.unlisten(src, listener, opt_capt, opt_handler);
};


/**
 * Cleans up goog.events internal data structure. This should be
 * called by all implementations of goog.events.Listenable when
 * removing listeners.
 *
 * TODO(user): Once we remove numeric key support from
 * goog.events.listen and friend, we will be able to remove this
 * requirement.
 *
 * @param {goog.events.ListenableKey} listenableKey The key to clean up.
 */
goog.events.cleanUp = function(listenableKey) {
  delete goog.events.listeners_[listenableKey.key];
};


/**
 * Cleans up the listener array as well as the listener tree
 * @param {string} type  The type of the event.
 * @param {boolean} capture Whether to clean up capture phase listeners instead
 *     bubble phase listeners.
 * @param {number} srcUid  The unique ID of the source.
 * @param {Array.<goog.events.Listener>} listenerArray The array being cleaned.
 * @private
 */
goog.events.cleanUp_ = function(type, capture, srcUid, listenerArray) {
  // The listener array gets locked during the dispatch phase so that removals
  // of listeners during this phase does not screw up the indeces. This method
  // is called after we have removed a listener as well as after the dispatch
  // phase in case any listeners were removed.
  if (!listenerArray.locked_) { // catches both 0 and not set
    if (listenerArray.needsCleanup_) {
      // Loop over the listener array and remove listeners that have removed set
      // to true. This could have been done with filter or something similar but
      // we want to change the array in place and we want to minimize
      // allocations. Adding a listener during this phase adds to the end of the
      // array so that works fine as long as the length is rechecked every in
      // iteration.
      for (var oldIndex = 0, newIndex = 0;
           oldIndex < listenerArray.length;
           oldIndex++) {
        if (listenerArray[oldIndex].removed) {
          continue;
        }
        if (oldIndex != newIndex) {
          listenerArray[newIndex] = listenerArray[oldIndex];
        }
        newIndex++;
      }
      listenerArray.length = newIndex;

      listenerArray.needsCleanup_ = false;

      // In case the length is now zero we release the object.
      if (newIndex == 0) {
        delete goog.events.listenerTree_[type][capture][srcUid];
        goog.events.listenerTree_[type][capture].count_--;

        if (goog.events.listenerTree_[type][capture].count_ == 0) {
          delete goog.events.listenerTree_[type][capture];
          goog.events.listenerTree_[type].count_--;
        }

        if (goog.events.listenerTree_[type].count_ == 0) {
          delete goog.events.listenerTree_[type];
        }
      }

    }
  }
};


/**
 * Removes all listeners from an object. You can also optionally
 * remove listeners of a particular type.
 *
 * @param {Object=} opt_obj Object to remove listeners from. Not
 *     specifying opt_obj is now DEPRECATED (it used to remove all
 *     registered listeners).
 * @param {string=} opt_type Type of event to, default is all types.
 * @return {number} Number of listeners removed.
 */
goog.events.removeAll = function(opt_obj, opt_type) {
  var count = 0;

  var noObj = opt_obj == null;
  var noType = opt_type == null;

  if (!noObj) {
    if (opt_obj && goog.events.Listenable.isImplementedBy(opt_obj)) {
      return opt_obj.removeAllListeners(opt_type);
    }

    var srcUid = goog.getUid(/** @type {Object} */ (opt_obj));
    if (goog.events.sources_[srcUid]) {
      var sourcesArray = goog.events.sources_[srcUid];
      for (var i = sourcesArray.length - 1; i >= 0; i--) {
        var listener = sourcesArray[i];
        if (noType || opt_type == listener.type) {
          goog.events.unlistenByKey(listener);
          count++;
        }
      }
    }
  } else {
    goog.object.forEach(goog.events.listeners_, function(listener) {
      goog.events.unlistenByKey(listener);
      count++;
    });
  }

  return count;
};


/**
 * Removes all native listeners registered via goog.events. Native
 * listeners are listeners on native browser objects (such as DOM
 * elements). In particular, goog.events.Listenable and
 * goog.events.EventTarget listeners will NOT be removed.
 * @return {number} Number of listeners removed.
 */
goog.events.removeAllNativeListeners = function() {
  var count = 0;
  goog.object.forEach(goog.events.listeners_, function(listener) {
    var src = listener.src;
    // Only remove the listener if it is not on custom event target.
    if (!goog.events.Listenable.isImplementedBy(src)) {
      goog.events.unlistenByKey(listener);
      count++;
    }
  });
  return count;
};


/**
 * Gets the listeners for a given object, type and capture phase.
 *
 * @param {Object} obj Object to get listeners for.
 * @param {string} type Event type.
 * @param {boolean} capture Capture phase?.
 * @return {Array.<goog.events.Listener>} Array of listener objects.
 */
goog.events.getListeners = function(obj, type, capture) {
  if (goog.events.Listenable.isImplementedBy(obj)) {
    return obj.getListeners(type, capture);
  } else {
    return goog.events.getListeners_(obj, type, capture) || [];
  }
};


/**
 * Gets the listeners for a given object, type and capture phase.
 *
 * @param {Object} obj Object to get listeners for.
 * @param {?string} type Event type.
 * @param {boolean} capture Capture phase?.
 * @return {Array.<goog.events.Listener>?} Array of listener objects.
 *     Returns null if object has no listeners of that type.
 * @private
 */
goog.events.getListeners_ = function(obj, type, capture) {
  var map = goog.events.listenerTree_;
  if (type in map) {
    map = map[type];
    if (capture in map) {
      map = map[capture];
      var objUid = goog.getUid(obj);
      if (map[objUid]) {
        return map[objUid];
      }
    }
  }

  return null;
};


/**
 * Gets the goog.events.Listener for the event or null if no such listener is
 * in use.
 *
 * @param {goog.events.ListenableType} src The target from which to get
 *     listeners.
 * @param {?string} type The name of the event without the 'on' prefix.
 * @param {Function|Object} listener The listener function to get.
 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
 *                            whether the listener is fired during the
 *                            capture or bubble phase of the event.
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.ListenableKey} the found listener or null if not found.
 */
goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
  var capture = !!opt_capt;

  listener = goog.events.wrapListener_(listener);
  if (goog.events.Listenable.isImplementedBy(src)) {
    return src.getListener(
        /** @type {string} */ (type), listener, capture, opt_handler);
  }

  var listenerArray = goog.events.getListeners_(src, type, capture);
  if (listenerArray) {
    for (var i = 0; i < listenerArray.length; i++) {
      // If goog.events.unlistenByKey is called during an event dispatch
      // then the listener array won't get cleaned up and there might be
      // 'removed' listeners in the list. Ignore those.
      if (!listenerArray[i].removed &&
          listenerArray[i].listener == listener &&
          listenerArray[i].capture == capture &&
          listenerArray[i].handler == opt_handler) {
        // We already have this listener. Return its key.
        return listenerArray[i];
      }
    }
  }
  return null;
};


/**
 * Returns whether an event target has any active listeners matching the
 * specified signature. If either the type or capture parameters are
 * unspecified, the function will match on the remaining criteria.
 *
 * @param {goog.events.ListenableType} obj Target to get listeners for.
 * @param {string=} opt_type Event type.
 * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
 *     listeners.
 * @return {boolean} Whether an event target has one or more listeners matching
 *     the requested type and/or capture phase.
 */
goog.events.hasListener = function(obj, opt_type, opt_capture) {
  if (goog.events.Listenable.isImplementedBy(obj)) {
    return obj.hasListener(opt_type, opt_capture);
  }

  var objUid = goog.getUid(obj);
  var listeners = goog.events.sources_[objUid];

  if (listeners) {
    var hasType = goog.isDef(opt_type);
    var hasCapture = goog.isDef(opt_capture);

    if (hasType && hasCapture) {
      // Lookup in the listener tree whether the specified listener exists.
      var map = goog.events.listenerTree_[opt_type];
      return !!map && !!map[opt_capture] && objUid in map[opt_capture];

    } else if (!(hasType || hasCapture)) {
      // Simple check for whether the event target has any listeners at all.
      return true;

    } else {
      // Iterate through the listeners for the event target to find a match.
      return goog.array.some(listeners, function(listener) {
        return (hasType && listener.type == opt_type) ||
               (hasCapture && listener.capture == opt_capture);
      });
    }
  }

  return false;
};


/**
 * Provides a nice string showing the normalized event objects public members
 * @param {Object} e Event Object.
 * @return {string} String of the public members of the normalized event object.
 */
goog.events.expose = function(e) {
  var str = [];
  for (var key in e) {
    if (e[key] && e[key].id) {
      str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
    } else {
      str.push(key + ' = ' + e[key]);
    }
  }
  return str.join('\n');
};


/**
 * Returns a string wth on prepended to the specified type. This is used for IE
 * which expects "on" to be prepended. This function caches the string in order
 * to avoid extra allocations in steady state.
 * @param {string} type Event type strng.
 * @return {string} The type string with 'on' prepended.
 * @private
 */
goog.events.getOnString_ = function(type) {
  if (type in goog.events.onStringMap_) {
    return goog.events.onStringMap_[type];
  }
  return goog.events.onStringMap_[type] = goog.events.onString_ + type;
};


/**
 * Fires an object's listeners of a particular type and phase
 *
 * @param {Object} obj Object whose listeners to call.
 * @param {string} type Event type.
 * @param {boolean} capture Which event phase.
 * @param {Object} eventObject Event object to be passed to listener.
 * @return {boolean} True if all listeners returned true else false.
 */
goog.events.fireListeners = function(obj, type, capture, eventObject) {
  if (goog.events.Listenable.isImplementedBy(obj)) {
    return obj.fireListeners(type, capture, eventObject);
  }

  var map = goog.events.listenerTree_;
  if (type in map) {
    map = map[type];
    if (capture in map) {
      return goog.events.fireListeners_(map[capture], obj, type,
                                        capture, eventObject);
    }
  }
  return true;
};


/**
 * Fires an object's listeners of a particular type and phase.
 *
 * @param {Object} map Object with listeners in it.
 * @param {Object} obj Object whose listeners to call.
 * @param {string} type Event type.
 * @param {boolean} capture Which event phase.
 * @param {Object} eventObject Event object to be passed to listener.
 * @return {boolean} True if all listeners returned true else false.
 * @private
 */
goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) {
  var retval = 1;

  var objUid = goog.getUid(obj);
  if (map[objUid]) {
    var remaining = --map.remaining_;
    var listenerArray = map[objUid];

    // If locked_ is not set (and if already 0) initialize it to 1.
    if (!listenerArray.locked_) {
      listenerArray.locked_ = 1;
    } else {
      listenerArray.locked_++;
    }

    try {
      // Events added in the dispatch phase should not be dispatched in
      // the current dispatch phase. They will be included in the next
      // dispatch phase though.
      var length = listenerArray.length;
      for (var i = 0; i < length; i++) {
        var listener = listenerArray[i];
        // We might not have a listener if the listener was removed.
        if (listener && !listener.removed) {
          retval &=
              goog.events.fireListener(listener, eventObject) !== false;
        }
      }
    } finally {
      // Allow the count of targets remaining to increase (if perhaps we have
      // added listeners) but do not allow it to decrease if we have reentered
      // this method through a listener dispatching the same event type,
      // resetting and exhausted the remaining count.
      map.remaining_ = Math.max(remaining, map.remaining_);
      listenerArray.locked_--;
      goog.events.cleanUp_(type, capture, objUid, listenerArray);
    }
  }

  return Boolean(retval);
};


/**
 * Fires a listener with a set of arguments
 *
 * @param {goog.events.Listener} listener The listener object to call.
 * @param {Object} eventObject The event object to pass to the listener.
 * @return {boolean} Result of listener.
 */
goog.events.fireListener = function(listener, eventObject) {
  var listenerFn = listener.listener;
  var listenerHandler = listener.handler || listener.src;

  if (listener.callOnce) {
    goog.events.unlistenByKey(listener);
  }
  return listenerFn.call(listenerHandler, eventObject);
};


/**
 * Gets the total number of listeners currently in the system.
 * @return {number} Number of listeners.
 */
goog.events.getTotalListenerCount = function() {
  return goog.object.getCount(goog.events.listeners_);
};


/**
 * Dispatches an event (or event like object) and calls all listeners
 * listening for events of this type. The type of the event is decided by the
 * type property on the event object.
 *
 * If any of the listeners returns false OR calls preventDefault then this
 * function will return false.  If one of the capture listeners calls
 * stopPropagation, then the bubble listeners won't fire.
 *
 * @param {goog.events.Listenable} src The event target.
 * @param {goog.events.EventLike} e Event object.
 * @return {boolean} If anyone called preventDefault on the event object (or
 *     if any of the handlers returns false) this will also return false.
 *     If there are no handlers, or if all handlers return true, this returns
 *     true.
 */
goog.events.dispatchEvent = function(src, e) {
  goog.asserts.assert(
      goog.events.Listenable.isImplementedBy(src),
      'Can not use goog.events.dispatchEvent with ' +
      'non-goog.events.Listenable instance.');
  return src.dispatchEvent(e);
};


/**
 * Installs exception protection for the browser event entry point using the
 * given error handler.
 *
 * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
 *     protect the entry point.
 */
goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
  goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
      goog.events.handleBrowserEvent_);
};


/**
 * Handles an event and dispatches it to the correct listeners. This
 * function is a proxy for the real listener the user specified.
 *
 * @param {goog.events.Listener} listener The listener object.
 * @param {Event=} opt_evt Optional event object that gets passed in via the
 *     native event handlers.
 * @return {boolean} Result of the event handler.
 * @this {goog.events.EventTarget|Object} The object or Element that
 *     fired the event. TODO(user): Figure out why the type is
 *     so weird?
 * @private
 */
goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
  if (listener.removed) {
    return true;
  }

  var type = listener.type;
  var map = goog.events.listenerTree_;

  if (!(type in map)) {
    return true;
  }
  map = map[type];
  var retval, targetsMap;
  // Synthesize event propagation if the browser does not support W3C
  // event model.
  if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
    var ieEvent = opt_evt ||
        /** @type {Event} */ (goog.getObjectByName('window.event'));

    // Check if we have any capturing event listeners for this type.
    var hasCapture = true in map;
    var hasBubble = false in map;

    if (hasCapture) {
      if (goog.events.isMarkedIeEvent_(ieEvent)) {
        return true;
      }

      goog.events.markIeEvent_(ieEvent);
    }

    var evt = new goog.events.BrowserEvent();
    // TODO(user): update @this for this function
    evt.init(ieEvent, /** @type {EventTarget} */ (this));

    retval = true;
    try {
      if (hasCapture) {
        var ancestors = [];

        for (var parent = evt.currentTarget;
             parent;
             parent = parent.parentNode) {
          ancestors.push(parent);
        }

        targetsMap = map[true];
        targetsMap.remaining_ = targetsMap.count_;

        // Call capture listeners
        for (var i = ancestors.length - 1;
             !evt.propagationStopped_ && i >= 0 && targetsMap.remaining_;
             i--) {
          evt.currentTarget = ancestors[i];
          retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type,
                                               true, evt);
        }

        if (hasBubble) {
          targetsMap = map[false];
          targetsMap.remaining_ = targetsMap.count_;

          // Call bubble listeners
          for (var i = 0;
               !evt.propagationStopped_ && i < ancestors.length &&
               targetsMap.remaining_;
               i++) {
            evt.currentTarget = ancestors[i];
            retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type,
                                                 false, evt);
          }
        }

      } else {
        // Bubbling, let IE handle the propagation.
        retval = goog.events.fireListener(listener, evt);
      }

    } finally {
      if (ancestors) {
        ancestors.length = 0;
      }
    }
    return retval;
  } // IE

  // Caught a non-IE DOM event. 1 additional argument which is the event object
  var be = new goog.events.BrowserEvent(
      opt_evt, /** @type {EventTarget} */ (this));
  retval = goog.events.fireListener(listener, be);
  return retval;
};


/**
 * This is used to mark the IE event object so we do not do the Closure pass
 * twice for a bubbling event.
 * @param {Event} e The IE browser event.
 * @private
 */
goog.events.markIeEvent_ = function(e) {
  // Only the keyCode and the returnValue can be changed. We use keyCode for
  // non keyboard events.
  // event.returnValue is a bit more tricky. It is undefined by default. A
  // boolean false prevents the default action. In a window.onbeforeunload and
  // the returnValue is non undefined it will be alerted. However, we will only
  // modify the returnValue for keyboard events. We can get a problem if non
  // closure events sets the keyCode or the returnValue

  var useReturnValue = false;

  if (e.keyCode == 0) {
    // We cannot change the keyCode in case that srcElement is input[type=file].
    // We could test that that is the case but that would allocate 3 objects.
    // If we use try/catch we will only allocate extra objects in the case of a
    // failure.
    /** @preserveTry */
    try {
      e.keyCode = -1;
      return;
    } catch (ex) {
      useReturnValue = true;
    }
  }

  if (useReturnValue ||
      /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
    e.returnValue = true;
  }
};


/**
 * This is used to check if an IE event has already been handled by the Closure
 * system so we do not do the Closure pass twice for a bubbling event.
 * @param {Event} e  The IE browser event.
 * @return {boolean} True if the event object has been marked.
 * @private
 */
goog.events.isMarkedIeEvent_ = function(e) {
  return e.keyCode < 0 || e.returnValue != undefined;
};


/**
 * Counter to create unique event ids.
 * @type {number}
 * @private
 */
goog.events.uniqueIdCounter_ = 0;


/**
 * Creates a unique event id.
 *
 * @param {string} identifier The identifier.
 * @return {string} A unique identifier.
 * @idGenerator
 */
goog.events.getUniqueId = function(identifier) {
  return identifier + '_' + goog.events.uniqueIdCounter_++;
};


/**
 * Expando property for listener function wrapper for Object with
 * handleEvent.
 * @type {string}
 * @private
 */
goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
    ((Math.random() * 1e9) >>> 0);


/**
 * @param {Object|Function} listener The listener function or an
 *     object that contains handleEvent method.
 * @return {!Function} Either the original function or a function that
 *     calls obj.handleEvent. If the same listener is passed to this
 *     function more than once, the same function is guaranteed to be
 *     returned.
 * @private
 */
goog.events.wrapListener_ = function(listener) {
  goog.asserts.assert(listener, 'Listener can not be null.');

  if (goog.isFunction(listener)) {
    return listener;
  }

  goog.asserts.assert(
      listener.handleEvent, 'An object listener must have handleEvent method.');
  return listener[goog.events.LISTENER_WRAPPER_PROP_] ||
      (listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) {
        return listener.handleEvent(e);
      });
};


// Register the browser event handler as an entry point, so that
// it can be monitored for exception handling, etc.
goog.debug.entryPointRegistry.register(
    /**
     * @param {function(!Function): !Function} transformer The transforming
     *     function.
     */
    function(transformer) {
      goog.events.handleBrowserEvent_ = transformer(
          goog.events.handleBrowserEvent_);
    });

// Input 135
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Class to create objects which want to handle multiple events
 * and have their listeners easily cleaned up via a dispose method.
 *
 * Example:
 * <pre>
 * function Something() {
 *   goog.base(this);
 *
 *   ... set up object ...
 *
 *   // Add event listeners
 *   this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
 *   this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
 *   this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
 *   this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
 *   this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
 * }
 * goog.inherits(Something, goog.events.EventHandler);
 *
 * Something.prototype.disposeInternal = function() {
 *   goog.base(this, 'disposeInternal');
 *   goog.dom.removeNode(this.container);
 * };
 *
 *
 * // Then elsewhere:
 *
 * var activeSomething = null;
 * function openSomething() {
 *   activeSomething = new Something();
 * }
 *
 * function closeSomething() {
 *   if (activeSomething) {
 *     activeSomething.dispose();  // Remove event listeners
 *     activeSomething = null;
 *   }
 * }
 * </pre>
 *
 */

goog.provide('goog.events.EventHandler');

goog.require('goog.Disposable');
goog.require('goog.events');
goog.require('goog.object');



/**
 * Super class for objects that want to easily manage a number of event
 * listeners.  It allows a short cut to listen and also provides a quick way
 * to remove all events listeners belonging to this object.
 * @param {Object=} opt_handler Object in whose scope to call the listeners.
 * @constructor
 * @extends {goog.Disposable}
 */
goog.events.EventHandler = function(opt_handler) {
  goog.Disposable.call(this);
  this.handler_ = opt_handler;

  /**
   * Keys for events that are being listened to.
   * @type {!Object.<!goog.events.Key>}
   * @private
   */
  this.keys_ = {};
};
goog.inherits(goog.events.EventHandler, goog.Disposable);


/**
 * Utility array used to unify the cases of listening for an array of types
 * and listening for a single event, without using recursion or allocating
 * an array each time.
 * @type {Array.<string>}
 * @private
 */
goog.events.EventHandler.typeArray_ = [];


/**
 * Listen to an event on a Listenable.  If the function is omitted then the
 * EventHandler's handleEvent method will be used.
 * @param {goog.events.ListenableType} src Event source.
 * @param {string|Array.<string>} type Event type to listen for or array of
 *     event types.
 * @param {Function|Object=} opt_fn Optional callback function to be used as the
 *    listener or an object with handleEvent function.
 * @param {boolean=} opt_capture Optional whether to use capture phase.
 * @param {Object=} opt_handler Object in whose scope to call the listener.
 * @return {goog.events.EventHandler} This object, allowing for chaining of
 *     calls.
 */
goog.events.EventHandler.prototype.listen = function(src, type, opt_fn,
                                                     opt_capture,
                                                     opt_handler) {
  if (!goog.isArray(type)) {
    goog.events.EventHandler.typeArray_[0] = /** @type {string} */(type);
    type = goog.events.EventHandler.typeArray_;
  }
  for (var i = 0; i < type.length; i++) {
    var listenerObj = goog.events.listen(
        src, type[i], opt_fn || this,
        opt_capture || false,
        opt_handler || this.handler_ || this);

    if (goog.DEBUG && !listenerObj) {
      // Some tests mock goog.events.listen, thus ensuring that
      // they are never testing the real thing anyway, hence this is safe
      // (except that #getListenerCount() will return the wrong value).
      return this;
    }

    var key = listenerObj.key;
    this.keys_[key] = listenerObj;
  }

  return this;
};


/**
 * Listen to an event on a Listenable.  If the function is omitted, then the
 * EventHandler's handleEvent method will be used. After the event has fired the
 * event listener is removed from the target. If an array of event types is
 * provided, each event type will be listened to once.
 * @param {goog.events.ListenableType} src Event source.
 * @param {string|Array.<string>} type Event type to listen for or array of
 *     event types.
 * @param {Function|Object=} opt_fn Optional callback function to be used as the
 *    listener or an object with handleEvent function.
 * @param {boolean=} opt_capture Optional whether to use capture phase.
 * @param {Object=} opt_handler Object in whose scope to call the listener.
 * @return {goog.events.EventHandler} This object, allowing for chaining of
 *     calls.
 */
goog.events.EventHandler.prototype.listenOnce = function(src, type, opt_fn,
                                                         opt_capture,
                                                         opt_handler) {
  if (goog.isArray(type)) {
    for (var i = 0; i < type.length; i++) {
      this.listenOnce(src, type[i], opt_fn, opt_capture, opt_handler);
    }
  } else {
    var listenerObj = goog.events.listenOnce(
        src, type, opt_fn || this, opt_capture,
        opt_handler || this.handler_ || this);
    var key = listenerObj.key;
    this.keys_[key] = listenerObj;
  }

  return this;
};


/**
 * Adds an event listener with a specific event wrapper on a DOM Node or an
 * object that has implemented {@link goog.events.EventTarget}. A listener can
 * only be added once to an object.
 *
 * @param {EventTarget|goog.events.EventTarget} src The node to listen to
 *     events on.
 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
 * @param {Function|Object} listener Callback method, or an object with a
 *     handleEvent function.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.EventHandler} This object, allowing for chaining of
 *     calls.
 */
goog.events.EventHandler.prototype.listenWithWrapper = function(src, wrapper,
    listener, opt_capt, opt_handler) {
  wrapper.listen(src, listener, opt_capt, opt_handler || this.handler_ || this,
                 this);
  return this;
};


/**
 * @return {number} Number of listeners registered by this handler.
 */
goog.events.EventHandler.prototype.getListenerCount = function() {
  var count = 0;
  for (var key in this.keys_) {
    if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
      count++;
    }
  }
  return count;
};


/**
 * Unlistens on an event.
 * @param {goog.events.ListenableType} src Event source.
 * @param {string|Array.<string>} type Event type to listen for.
 * @param {Function|Object=} opt_fn Optional callback function to be used as the
 *    listener or an object with handleEvent function.
 * @param {boolean=} opt_capture Optional whether to use capture phase.
 * @param {Object=} opt_handler Object in whose scope to call the listener.
 * @return {goog.events.EventHandler} This object, allowing for chaining of
 *     calls.
 */
goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn,
                                                       opt_capture,
                                                       opt_handler) {
  if (goog.isArray(type)) {
    for (var i = 0; i < type.length; i++) {
      this.unlisten(src, type[i], opt_fn, opt_capture, opt_handler);
    }
  } else {
    var listener = goog.events.getListener(src, type, opt_fn || this,
        opt_capture, opt_handler || this.handler_ || this);

    if (listener) {
      goog.events.unlistenByKey(listener);
      delete this.keys_[listener.key];
    }
  }

  return this;
};


/**
 * Removes an event listener which was added with listenWithWrapper().
 *
 * @param {EventTarget|goog.events.EventTarget} src The target to stop
 *     listening to events on.
 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
 * @param {Function|Object} listener The listener function to remove.
 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase of the
 *     event.
 * @param {Object=} opt_handler Element in whose scope to call the listener.
 * @return {goog.events.EventHandler} This object, allowing for chaining of
 *     calls.
 */
goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper,
    listener, opt_capt, opt_handler) {
  wrapper.unlisten(src, listener, opt_capt,
                   opt_handler || this.handler_ || this, this);
  return this;
};


/**
 * Unlistens to all events.
 */
goog.events.EventHandler.prototype.removeAll = function() {
  goog.object.forEach(this.keys_, goog.events.unlistenByKey);
  this.keys_ = {};
};


/**
 * Disposes of this EventHandler and removes all listeners that it registered.
 * @override
 * @protected
 */
goog.events.EventHandler.prototype.disposeInternal = function() {
  goog.events.EventHandler.superClass_.disposeInternal.call(this);
  this.removeAll();
};


/**
 * Default event handler
 * @param {goog.events.Event} e Event object.
 */
goog.events.EventHandler.prototype.handleEvent = function(e) {
  throw Error('EventHandler.handleEvent not implemented');
};

// Input 136
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Implementation of EventTarget as defined by W3C DOM 2/3.
 *
 * @author arv@google.com (Erik Arvidsson) [Original implementation]
 * @author pupius@google.com (Daniel Pupius) [Port to use goog.events]
 * @see ../demos/eventtarget.html
 */

goog.provide('goog.events.EventTarget');

goog.require('goog.Disposable');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.Listenable');
goog.require('goog.events.Listener');
goog.require('goog.object');



/**
 * Inherit from this class to give your object the ability to dispatch events.
 * Note that this class provides event <em>sending</em> behaviour, not event
 * receiving behaviour: your object will be able to broadcast events, and other
 * objects will be able to listen for those events using goog.events.listen().
 *
 * <p>The name "EventTarget" reflects the fact that this class implements the
 * <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html">
 * EventTarget interface</a> as defined by W3C DOM 2/3, with a few differences:
 * <ul>
 * <li>Event objects do not have to implement the Event interface. An object
 *     is treated as an event object if it has a 'type' property.
 * <li>You can use a plain string instead of an event object; an event-like
 *     object will be created with the 'type' set to the string value.
 * </ul>
 *
 * <p>Unless propagation is stopped, an event dispatched by an EventTarget
 * will bubble to the parent returned by <code>getParentEventTarget</code>.
 * To set the parent, call <code>setParentEventTarget</code> or override
 * <code>getParentEventTarget</code> in a subclass.  Subclasses that don't
 * support changing the parent should override the setter to throw an error.
 *
 * <p>Example usage:
 * <pre>
 *   var source = new goog.events.EventTarget();
 *   function handleEvent(event) {
 *     alert('Type: ' + e.type + '\nTarget: ' + e.target);
 *   }
 *   goog.events.listen(source, 'foo', handleEvent);
 *   ...
 *   source.dispatchEvent({type: 'foo'}); // will call handleEvent
 *   // or source.dispatchEvent('foo');
 *   ...
 *   goog.events.unlisten(source, 'foo', handleEvent);
 *
 *   // You can also use the Listener interface:
 *   var listener = {
 *     handleEvent: function(event) {
 *       ...
 *     }
 *   };
 *   goog.events.listen(source, 'bar', listener);
 * </pre>
 *
 * @constructor
 * @extends {goog.Disposable}
 * @implements {goog.events.Listenable}
 */
goog.events.EventTarget = function() {
  goog.Disposable.call(this);

  /**
   * Maps of event type to an array of listeners.
   *
   * @type {Object.<string, !Array.<!goog.events.Listener>>}
   * @private
   */
  this.eventTargetListeners_ = {};

  /**
   * The object to use for event.target. Useful when mixing in an
   * EventTarget to another object.
   * @type {!Object}
   * @private
   */
  this.actualEventTarget_ = this;
};
goog.inherits(goog.events.EventTarget, goog.Disposable);
goog.events.Listenable.addImplementation(goog.events.EventTarget);


/**
 * An artificial cap on the number of ancestors you can have. This is mainly
 * for loop detection.
 * @const {number}
 * @private
 */
goog.events.EventTarget.MAX_ANCESTORS_ = 1000;


/**
 * Parent event target, used during event bubbling.
 * @type {goog.events.EventTarget?}
 * @private
 */
goog.events.EventTarget.prototype.parentEventTarget_ = null;


/**
 * Returns the parent of this event target to use for bubbling.
 *
 * @return {goog.events.EventTarget} The parent EventTarget or null if there
 * is no parent.
 */
goog.events.EventTarget.prototype.getParentEventTarget = function() {
  return this.parentEventTarget_;
};


/**
 * Sets the parent of this event target to use for bubbling.
 *
 * @param {goog.events.EventTarget?} parent Parent EventTarget (null if none).
 */
goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
  this.parentEventTarget_ = parent;
};


/**
 * Adds an event listener to the event target. The same handler can only be
 * added once per the type. Even if you add the same handler multiple times
 * using the same type then it will only be called once when the event is
 * dispatched.
 *
 * Supported for legacy but use goog.events.listen(src, type, handler) instead.
 *
 * TODO(user): Deprecate this.
 *
 * @param {string} type The type of the event to listen for.
 * @param {Function|Object} handler The function to handle the event. The
 *     handler can also be an object that implements the handleEvent method
 *     which takes the event object as argument.
 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase
 *     of the event.
 * @param {Object=} opt_handlerScope Object in whose scope to call
 *     the listener.
 */
goog.events.EventTarget.prototype.addEventListener = function(
    type, handler, opt_capture, opt_handlerScope) {
  goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
};


/**
 * Removes an event listener from the event target. The handler must be the
 * same object as the one added. If the handler has not been added then
 * nothing is done.
 *
 * TODO(user): Deprecate this.
 *
 * @param {string} type The type of the event to listen for.
 * @param {Function|Object} handler The function to handle the event. The
 *     handler can also be an object that implements the handleEvent method
 *     which takes the event object as argument.
 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase
 *     of the event.
 * @param {Object=} opt_handlerScope Object in whose scope to call
 *     the listener.
 */
goog.events.EventTarget.prototype.removeEventListener = function(
    type, handler, opt_capture, opt_handlerScope) {
  goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
};


/** @override */
goog.events.EventTarget.prototype.dispatchEvent = function(e) {
  this.assertInitialized_();

  var ancestorsTree, ancestor = this.getParentEventTarget();
  if (ancestor) {
    ancestorsTree = [];
    var ancestorCount = 1;
    for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
      ancestorsTree.push(ancestor);
      goog.asserts.assert(
          (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
          'infinite loop');
    }
  }

  return goog.events.EventTarget.dispatchEventInternal_(
      this.actualEventTarget_, e, ancestorsTree);
};


/**
 * Unattach listeners from this object.  Classes that extend EventTarget may
 * need to override this method in order to remove references to DOM Elements
 * and additional listeners, it should be something like this:
 * <pre>
 * MyClass.prototype.disposeInternal = function() {
 *   MyClass.superClass_.disposeInternal.call(this);
 *   // Dispose logic for MyClass
 * };
 * </pre>
 * @override
 * @protected
 */
goog.events.EventTarget.prototype.disposeInternal = function() {
  goog.events.EventTarget.superClass_.disposeInternal.call(this);

  this.removeAllListeners();
  this.parentEventTarget_ = null;
};


/**
 * Asserts that the event target instance is initialized properly.
 * @private
 */
goog.events.EventTarget.prototype.assertInitialized_ = function() {
  goog.asserts.assert(
      this.eventTargetListeners_,
      'Event target is not initialized. Did you call superclass ' +
      '(goog.events.EventTarget) constructor?');
};


/** @override */
goog.events.EventTarget.prototype.listen = function(
    type, listener, opt_useCapture, opt_listenerScope) {
  return this.listenInternal_(
      type, listener, false /* callOnce */, opt_useCapture, opt_listenerScope);
};


/** @override */
goog.events.EventTarget.prototype.listenOnce = function(
    type, listener, opt_useCapture, opt_listenerScope) {
  return this.listenInternal_(
      type, listener, true /* callOnce */, opt_useCapture, opt_listenerScope);
};


/**
 * Adds an event listener. A listener can only be added once to an
 * object and if it is added again the key for the listener is
 * returned.
 *
 * Note that a one-off listener will not change an existing listener,
 * if any. On the other hand a normal listener will change existing
 * one-off listener to become a normal listener.
 *
 * @param {string} type Event type to listen to.
 * @param {!Function} listener Callback method.
 * @param {boolean} callOnce Whether the listener is a one-off
 *     listener or otherwise.
 * @param {boolean=} opt_useCapture Whether to fire in capture phase
 *     (defaults to false).
 * @param {Object=} opt_listenerScope Object in whose scope to call the
 *     listener.
 * @return {goog.events.ListenableKey} Unique key for the listener.
 * @private
 */
goog.events.EventTarget.prototype.listenInternal_ = function(
    type, listener, callOnce, opt_useCapture, opt_listenerScope) {
  this.assertInitialized_();

  var listenerArray = this.eventTargetListeners_[type] ||
      (this.eventTargetListeners_[type] = []);

  var listenerObj;
  var index = goog.events.EventTarget.findListenerIndex_(
      listenerArray, listener, opt_useCapture, opt_listenerScope);
  if (index > -1) {
    listenerObj = listenerArray[index];
    if (!callOnce) {
      // Ensure that, if there is an existing callOnce listener, it is no
      // longer a callOnce listener.
      listenerObj.callOnce = false;
    }
    return listenerObj;
  }

  listenerObj = new goog.events.Listener(
      listener, null, this, type, !!opt_useCapture, opt_listenerScope);
  listenerObj.callOnce = callOnce;
  listenerArray.push(listenerObj);

  return listenerObj;
};


/** @override */
goog.events.EventTarget.prototype.unlisten = function(
    type, listener, opt_useCapture, opt_listenerScope) {
  if (!(type in this.eventTargetListeners_)) {
    return false;
  }

  var listenerArray = this.eventTargetListeners_[type];
  var index = goog.events.EventTarget.findListenerIndex_(
      listenerArray, listener, opt_useCapture, opt_listenerScope);
  if (index > -1) {
    var listenerObj = listenerArray[index];
    goog.events.cleanUp(listenerObj);
    listenerObj.removed = true;
    return goog.array.removeAt(listenerArray, index);
  }
  return false;
};


/** @override */
goog.events.EventTarget.prototype.unlistenByKey = function(key) {
  var type = key.type;
  if (!(type in this.eventTargetListeners_)) {
    return false;
  }

  var removed = goog.array.remove(this.eventTargetListeners_[type], key);
  if (removed) {
    goog.events.cleanUp(key);
    key.removed = true;
  }
  return removed;
};


/** @override */
goog.events.EventTarget.prototype.removeAllListeners = function(
    opt_type, opt_capture) {
  var count = 0;
  for (var type in this.eventTargetListeners_) {
    if (!opt_type || type == opt_type) {
      var listenerArray = this.eventTargetListeners_[type];
      for (var i = 0; i < listenerArray.length; i++) {
        ++count;
        goog.events.cleanUp(listenerArray[i]);
        listenerArray[i].removed = true;
      }
      listenerArray.length = 0;
    }
  }
  return count;
};


/** @override */
goog.events.EventTarget.prototype.fireListeners = function(
    type, capture, eventObject) {
  if (!(type in this.eventTargetListeners_)) {
    return true;
  }

  var rv = true;
  var listenerArray = goog.array.clone(this.eventTargetListeners_[type]);
  for (var i = 0; i < listenerArray.length; ++i) {
    var listener = listenerArray[i];
    // We might not have a listener if the listener was removed.
    if (listener && !listener.removed && listener.capture == capture) {
      var listenerFn = listener.listener;
      var listenerHandler = listener.handler || listener.src;

      if (listener.callOnce) {
        this.unlistenByKey(listener);
      }
      rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
    }
  }

  return rv && eventObject.returnValue_ != false;
};


/** @override */
goog.events.EventTarget.prototype.getListeners = function(type, capture) {
  var listenerArray = this.eventTargetListeners_[type];
  var rv = [];
  if (listenerArray) {
    for (var i = 0; i < listenerArray.length; ++i) {
      var listenerObj = listenerArray[i];
      if (listenerObj.capture == capture) {
        rv.push(listenerObj);
      }
    }
  }
  return rv;
};


/** @override */
goog.events.EventTarget.prototype.getListener = function(
    type, listener, capture, opt_listenerScope) {
  var listenerArray = this.eventTargetListeners_[type];
  var i = -1;
  if (listenerArray) {
    i = goog.events.EventTarget.findListenerIndex_(
        listenerArray, listener, capture, opt_listenerScope);
  }
  return i > -1 ? listenerArray[i] : null;
};


/** @override */
goog.events.EventTarget.prototype.hasListener = function(
    opt_type, opt_capture) {
  var hasType = goog.isDef(opt_type);
  var hasCapture = goog.isDef(opt_capture);

  return goog.object.some(
      this.eventTargetListeners_, function(listenersArray, type) {
        for (var i = 0; i < listenersArray.length; ++i) {
          if ((!hasType || listenersArray[i].type == opt_type) &&
              (!hasCapture || listenersArray[i].capture == opt_capture)) {
            return true;
          }
        }

        return false;
      });
};


/**
 * Sets the target to be used for {@code event.target} when firing
 * event. Mainly used for testing. For example, see
 * {@code goog.testing.events.mixinListenable}.
 * @param {!Object} target The target.
 */
goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
  this.actualEventTarget_ = target;
};


/**
 * Dispatches the given event on the ancestorsTree.
 *
 * TODO(user): Look for a way to reuse this logic in
 * goog.events, if possible.
 *
 * @param {!Object} target The target to dispatch on.
 * @param {goog.events.Event|Object|string} e The event object.
 * @param {Array.<goog.events.Listenable>=} opt_ancestorsTree The ancestors
 *     tree of the target, in reverse order from the closest ancestor
 *     to the root event target. May be null if the target has no ancestor.
 * @return {boolean} If anyone called preventDefault on the event object (or
 *     if any of the listeners returns false) this will also return false.
 * @private
 */
goog.events.EventTarget.dispatchEventInternal_ = function(
    target, e, opt_ancestorsTree) {
  var type = e.type || /** @type {string} */ (e);

  // If accepting a string or object, create a custom event object so that
  // preventDefault and stopPropagation work with the event.
  if (goog.isString(e)) {
    e = new goog.events.Event(e, target);
  } else if (!(e instanceof goog.events.Event)) {
    var oldEvent = e;
    e = new goog.events.Event(type, target);
    goog.object.extend(e, oldEvent);
  } else {
    e.target = e.target || target;
  }

  var rv = true, currentTarget;

  // Executes all capture listeners on the ancestors, if any.
  if (opt_ancestorsTree) {
    for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
         i--) {
      currentTarget = e.currentTarget = opt_ancestorsTree[i];
      rv = currentTarget.fireListeners(type, true, e) && rv;
    }
  }

  // Executes capture and bubble listeners on the target.
  if (!e.propagationStopped_) {
    currentTarget = e.currentTarget = target;
    rv = currentTarget.fireListeners(type, true, e) && rv;
    if (!e.propagationStopped_) {
      rv = currentTarget.fireListeners(type, false, e) && rv;
    }
  }

  // Executes all bubble listeners on the ancestors, if any.
  if (opt_ancestorsTree) {
    for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
      currentTarget = e.currentTarget = opt_ancestorsTree[i];
      rv = currentTarget.fireListeners(type, false, e) && rv;
    }
  }

  return rv;
};


/**
 * Finds the index of a matching goog.events.Listener in the given
 * listenerArray.
 * @param {!Array.<!goog.events.Listener>} listenerArray Array of listener.
 * @param {!Function} listener The listener function.
 * @param {boolean=} opt_useCapture The capture flag for the listener.
 * @param {Object=} opt_listenerScope The listener scope.
 * @return {number} The index of the matching listener within the
 *     listenerArray.
 * @private
 */
goog.events.EventTarget.findListenerIndex_ = function(
    listenerArray, listener, opt_useCapture, opt_listenerScope) {
  for (var i = 0; i < listenerArray.length; ++i) {
    var listenerObj = listenerArray[i];
    if (listenerObj.listener == listener &&
        listenerObj.capture == !!opt_useCapture &&
        listenerObj.handler == opt_listenerScope) {
      return i;
    }
  }
  return -1;
};

// Input 137
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of the goog.events.EventWrapper interface.
 *
 * @author eae@google.com (Emil A Eklund)
 */

goog.provide('goog.events.EventWrapper');



/**
 * Interface for event wrappers.
 * @interface
 */
goog.events.EventWrapper = function() {
};


/**
 * Adds an event listener using the wrapper on a DOM Node or an object that has
 * implemented {@link goog.events.EventTarget}. A listener can only be added
 * once to an object.
 *
 * @param {goog.events.ListenableType} src The node to listen to events on.
 * @param {Function|Object} listener Callback method, or an object with a
 *     handleEvent function.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_scope Element in whose scope to call the listener.
 * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
 *     listener to.
 */
goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt,
    opt_scope, opt_eventHandler) {
};


/**
 * Removes an event listener added using goog.events.EventWrapper.listen.
 *
 * @param {goog.events.ListenableType} src The node to remove listener from.
 * @param {Function|Object} listener Callback method, or an object with a
 *     handleEvent function.
 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
 *     false).
 * @param {Object=} opt_scope Element in whose scope to call the listener.
 * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
 *     listener from.
 */
goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt,
    opt_scope, opt_eventHandler) {
};

// Input 138
/**
 * @license Copyright 2012 YDN Authors, Yathit. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");.
 */
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Install events.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */
goog.provide('ydn.db.tr.events');
goog.require('goog.debug.ErrorHandler');
goog.require('goog.events');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventWrapper');
goog.require('ydn.db.tr.Storage');


/**
 * @protected
 * @return {!goog.events.EventTarget}
 */
ydn.db.tr.Storage.prototype.getEventTarget = function() {
  if (!this.event_target) {
    this.event_target = new goog.events.EventTarget();
  }
  return /** @type {!goog.events.EventTarget} */ (this.event_target);
};


/**
 * Adds an event listener to the event target. The same handler can only be
 * added once per the type. Even if you add the same handler multiple times
 * using the same type then it will only be called once when the event is
 * dispatched.
 *
 * @param {string} type The type of the event to listen for.
 * @param {Function|Object} handler The function to handle the event. The
 *     handler can also be an object that implements the handleEvent method
 *     which takes the event object as argument.
 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase
 *     of the event.
 * @param {Object=} opt_handlerScope Object in whose scope to call
 *     the listener.
 */
ydn.db.tr.Storage.prototype.addEventListener = function(type, handler,
    opt_capture, opt_handlerScope) {
  if (type == 'ready') {
    // remove callback reference since 'ready' event is invoked only once.
    goog.events.listenOnce(this.getEventTarget(), type, handler, opt_capture,
        opt_handlerScope);
  } else {
    if (goog.DEBUG) {// don't allow to added non existing event type
      var event_types = this.getEventTypes();
      var checkType = function(type) {
        if (!goog.array.contains(event_types,
            type)) {
          throw new ydn.debug.error.ArgumentException('Invalid event type "' +
              type + '"');
        }
      };
      if (goog.isArrayLike(type)) {
        for (var i = 0; i < type.length; i++) {
          checkType(type[i]);
        }
      } else {
        checkType(type);
      }
    }
    goog.events.listen(this.getEventTarget(), type, handler, opt_capture,
        opt_handlerScope);
  }
};


/**
 * Removes an event listener from the event target. The handler must be the
 * same object as the one added. If the handler has not been added then
 * nothing is done.
 *
 * @param {string} type The type of the event to listen for.
 * @param {Function|Object} handler The function to handle the event. The
 *     handler can also be an object that implements the handleEvent method
 *     which takes the event object as argument.
 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
 *     whether the listener is fired during the capture or bubble phase
 *     of the event.
 * @param {Object=} opt_handlerScope Object in whose scope to call
 *     the listener.
 */
ydn.db.tr.Storage.prototype.removeEventListener = function(
    type, handler, opt_capture, opt_handlerScope) {
  goog.events.unlisten(this.getEventTarget(), type, handler, opt_capture, opt_handlerScope);
};


/**
 * @inheritDoc
 */
ydn.db.tr.Storage.prototype.dispatchDbEvent = function(event) {
  this.getEventTarget().dispatchEvent(event);
};


// Input 139
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Provide database query operations.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.Storage');
goog.require('ydn.db.Query');
goog.require('ydn.db.core.Storage.inject_executor');
goog.require('ydn.db.sql.Storage');
goog.require('ydn.db.sql.Storage.inject');
goog.require('ydn.db.tr.Storage.inject_db');
goog.require('ydn.db.tr.events');



/**
 * Create a suitable storage mechanism from indexdb, to websql to
 * localStorage.
 *
 * If database name and schema are provided, this will immediately initialize
 * the database and ready to use. However if any of these two are missing,
 * the database is not initialize until they are set by calling
 * {@link #setName} and {@link #setSchema}.
 * @see goog.db Google Closure Library DB module.
 * @param {string=} opt_dbname database name.
 * @param {(ydn.db.schema.Database|!DatabaseSchema)=} opt_schema database
 * schema
 * or its configuration in JSON format. If not provided, default empty schema
 * is used.
 * @param {!StorageOptions=} opt_options options.
 * @extends {ydn.db.sql.Storage}
 * @constructor *
 */
ydn.db.Storage = function(opt_dbname, opt_schema, opt_options) {
  goog.base(this, opt_dbname, opt_schema, opt_options);
};
goog.inherits(ydn.db.Storage, ydn.db.sql.Storage);




// Input 140
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.



/**
 * @fileoverview Naive Nested Loop Join algorithm.
 *
 * A simple nested-loop join (NLJ) algorithm reads rows from the first table in
 * a loop one at a time, passing each row to a nested loop that processes the
 * next table in the join. This process is repeated as many times as there
 * remain tables to be joined.
 *
 * Ref: http://dev.mysql.com/doc/refman/5.1/en/nested-loop-joins.html
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.algo.NestedLoop');
goog.require('ydn.db.algo.AbstractSolver');



/**
 *
 * @param {(!Array|!{push: Function}|!ydn.db.Streamer)=} out output receiver.
 * @param {number=} opt_limit limit.
 * @constructor
 * @extends {ydn.db.algo.AbstractSolver}
 */
ydn.db.algo.NestedLoop = function(out, opt_limit) {
  goog.base(this, out, opt_limit);
};
goog.inherits(ydn.db.algo.NestedLoop, ydn.db.algo.AbstractSolver);


/**
 * @define {boolean} debug flag.
 */
ydn.db.algo.NestedLoop.DEBUG = false;


/**
 * Index of active iterator.
 * @type {number}
 */
ydn.db.algo.NestedLoop.prototype.current_loop = -1;


/**
 * @inheritDoc
 */
ydn.db.algo.NestedLoop.prototype.begin = function(iterators, callback) {
  // we start with innermost loop.
  this.current_loop = iterators.length - 1;
  return false;
};


/**
 * @inheritDoc
 */
ydn.db.algo.NestedLoop.prototype.solver = function(keys, values) {

  // initialize advancement array
  var advancement = [];

  var all_restarting = true;

  var next = function(idx) {
    if (!goog.isDef(keys[idx])) {
      advancement[idx] = false; // restart
      if (idx - 1 >= 0) {
        next(idx - 1); // increase outer loop one step
      }
    } else {
      all_restarting = false;
      advancement[idx] = true;
    }
  };

  next(keys.length - 1); // increase one step to the innermost loop

  if (ydn.db.algo.NestedLoop.DEBUG) {
    window.console.log([keys, values, advancement]);
  }

  if (all_restarting) {
    advancement = []; // it is over.
  }

  return this.pusher(advancement, keys, values);
};

// Input 141
// Copyright 2012 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Zigzag merge algorithm.
 *
 * Zigzag merge join reference values of given composite index iterators (and
 * streamers) to matching value by continuing the last highest element of
 * effective values.
 *
 * Ref:
 * http://www.google.com/events/io/2010/sessions/next-gen-queries-appengine.html
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.db.algo.ZigzagMerge');
goog.require('ydn.db');



/**
 *
 * @param {(!Array|!{push: Function}|!ydn.db.Streamer)=} out output receiver.
 * @param {number=} opt_limit limit.
 * @constructor
 * @extends {ydn.db.algo.AbstractSolver}
 */
ydn.db.algo.ZigzagMerge = function(out, opt_limit) {
  goog.base(this, out, opt_limit);

  this.is_duplex_output_ = out instanceof ydn.db.Streamer &&
      !!out.getFieldName();
};
goog.inherits(ydn.db.algo.ZigzagMerge, ydn.db.algo.AbstractSolver);


/**
 * @define {boolean} debug flag.
 */
ydn.db.algo.ZigzagMerge.DEBUG = false;


/**
 * @protected
 * @type {goog.debug.Logger} logger.
 */
ydn.db.algo.ZigzagMerge.prototype.logger =
    goog.debug.Logger.getLogger('ydn.db.algo.ZigzagMerge');


/**
 *
 * @type {boolean}
 * @private
 */
ydn.db.algo.ZigzagMerge.prototype.is_duplex_output_ = false;


/**
 * @inheritDoc
 */
ydn.db.algo.ZigzagMerge.prototype.begin = function(iterators, callback) {
  var result = goog.base(this, 'begin', iterators, callback);
  if (this.is_duplex_output_) {
    var iter_index = iterators[0].getIndexName().split(', ');
    if (iter_index.length > 1) {
      if (iter_index[iter_index.length - 1] != this.out.getFieldName()) {
        throw new ydn.error.InvalidOperationError('Output streamer ' +
            'projection field must be same as postfix field in the iterator');
      }
    } else {
      if (goog.DEBUG) {
        this.logger.warning('Unable to check correctness of output streamer.');
      }
    }
  }
  return result;
};


/**
 * @inheritDoc
 */
ydn.db.algo.ZigzagMerge.prototype.solver = function(keys, values) {

  var advancement = [];

  if (keys.length == 0 || !goog.isDefAndNotNull(keys[0])) {
    return [];
  }

  /**
   * Return postfix value from the key.
   * @param {!Array} x the key.
   * @return {*}
   */
  var postfix = function(x) {
    return x[x.length - 1];
  };

  /**
   * Return prefix value from the key.
   * @param {!Array} x the key.
   * @return {!Array}
   */
  var prefix = function (x) {
    return x.slice(0, x.length - 1);
  };

  /**
   * Make full key from the prefix of given key and postfix parts.
   * @param {!Array} key original key.
   * @param {*} post_fix
   * @return {!Array} newly constructed key.
   */
  var makeKey = function(key, post_fix) {
    var new_key = prefix(key);
    new_key.push(post_fix);
    return new_key;
  };

  if (!goog.isDefAndNotNull(keys[0])) {
    if (ydn.db.algo.SortedMerge.DEBUG) {
      window.console.log('SortedMerge: done.');
    }
    return [];
  }
  var all_match = true; // let assume

  goog.asserts.assertArray(keys[0]);
  var highest_idx = 0;
  var highest_postfix = postfix(keys[highest_idx]);
  var cmps = [];

  for (var i = 1; i < keys.length; i++) {
    if (goog.isDefAndNotNull(keys[i])) {
      //console.log([values[0], keys[i]])
      var postfix_part = postfix(keys[i]);
      var cmp = ydn.db.cmp(highest_postfix, postfix_part);
      cmps[i] = cmp;
      if (this.is_reverse) {
        if (cmp == -1) {
          // base key is greater than ith key, so fast forward to ith key.
          all_match = false;
        } else if (cmp == 1) {
          // ith key is greater than base key. we are not going to get it
          all_match = false;
          highest_postfix = postfix_part;
          highest_idx = 1;
        }
      } else {
        if (cmp == 1) {
          // base key is greater than ith key, so fast forward to ith key.
          all_match = false;
        } else if (cmp == -1) {
          // ith key is greater than base key. we are not going to get it
          all_match = false;
          highest_postfix = postfix_part;
          highest_idx = 1;
        }
      }
      //i += this.degrees_[i]; // skip peer iterators.
    } else {
      if (ydn.db.algo.ZigzagMerge.DEBUG) {
        window.console.log(this + ': iterator ' + i + ' reach the end');
      }
      return [];
    }
  }

  if (ydn.db.algo.ZigzagMerge.DEBUG) {
    window.console.log('ZigzagMerge: match: ' + all_match +
        ', highest_key: ' + JSON.stringify(
        /** @type {Object} */ (highest_postfix)) +
        ', keys: ' + JSON.stringify(keys) +
        ', cmps: ' + JSON.stringify(cmps) +
        ', advancement: ' + JSON.stringify(advancement));
  }

  var cmp_target = this.is_reverse ? -1 : 1;
  if (all_match) {
    // all postfix key matched.
    // however result is the one when all primary keys are match.
    // since postfix key is index key, it may not be unique.
    // TODO: check matching primary keys and advance as necessary
    for (var j = 0; j < keys.length; j++) {
      if (goog.isDefAndNotNull(keys[j])) {
        advancement[j] = true;
      }
    }
    if (this.out) {
      if (this.is_duplex_output_) {
        this.out.push(values[0], highest_postfix);
      } else {
        this.out.push(values[0]);
      }
    }
    return advancement;
  } else if (highest_idx == 0) {
    // some need to catch up to base key
    for (var j = 1; j < keys.length; j++) {
      if (cmps[j] == cmp_target) {
        advancement[j] = makeKey(keys[j], highest_postfix);
      }
    }
  } else {
    // all jump to highest key position.
    for (var j = 0; j < keys.length; j++) {
      if (j == highest_idx) {
        continue;
      }
      if (goog.isDefAndNotNull(keys[j])) {
        // we need to compare again, because intermediate highest
        // key might get cmp value of 0, but not the highest key
        goog.asserts.assertArray(keys[j]);
        if (ydn.db.cmp(highest_postfix, postfix(keys[j])) === cmp_target) {
          advancement[j] = makeKey(keys[j], highest_postfix);
        }
      }
    }
  }

  return {'continue': advancement};

};

// Input 142
/**
 * @fileoverview Exports for main ydn-db algo module.
 *
 */

goog.provide('ydn.db.algo.exports');
goog.require('ydn.db.algo.NestedLoop');
goog.require('ydn.db.algo.SortedMerge');
goog.require('ydn.db.algo.ZigzagMerge');

goog.exportSymbol('ydn.db.algo.NestedLoop', ydn.db.algo.NestedLoop);
goog.exportSymbol('ydn.db.algo.ZigzagMerge', ydn.db.algo.ZigzagMerge);
goog.exportSymbol('ydn.db.algo.SortedMerge', ydn.db.algo.SortedMerge);


// Input 143
/**
 * @fileoverview Exports for ydn-db connection module.
 *
 * Exporting variable are defined in separate namespace so that closure
 * project can use this library without exporting any of ydn-db properties.
 * Project that want to export ydn-db properties should require this namespace.
 */


goog.provide('ydn.db.con.exports');
goog.require('ydn.base.exports');
goog.require('ydn.db');
goog.require('ydn.db.con.Storage');



// does not work for overridable function, use @expose instead
goog.exportProperty(ydn.db.con.Storage.prototype, 'close',
    ydn.db.con.Storage.prototype.close);
goog.exportProperty(ydn.db.con.Storage.prototype, 'getType',
    ydn.db.con.Storage.prototype.getType);
goog.exportProperty(ydn.db.con.Storage.prototype, 'getName',
    ydn.db.con.Storage.prototype.getName);
goog.exportProperty(ydn.db.con.Storage.prototype, 'getSchema',
    ydn.db.con.Storage.prototype.getSchema);
goog.exportProperty(ydn.db.con.Storage.prototype, 'onReady',
    ydn.db.con.Storage.prototype.onReady);
goog.exportProperty(ydn.db.con.Storage.prototype, 'setName',
    ydn.db.con.Storage.prototype.setName);
goog.exportProperty(ydn.db.con.Storage.prototype, 'transaction',
    ydn.db.con.Storage.prototype.transaction);

// for hacker only. This method should not document this, since this will change
// transaction state.
//goog.exportProperty(ydn.db.con.Storage.prototype, 'db',
//    ydn.db.con.Storage.prototype.getDbInstance);

goog.exportSymbol('ydn.db.version', ydn.db.version);
goog.exportSymbol('ydn.db.cmp', ydn.db.cmp);
goog.exportSymbol('ydn.db.deleteDatabase', ydn.db.deleteDatabase);

goog.exportProperty(ydn.db.events.StorageEvent.prototype, 'name',
    ydn.db.events.StorageEvent.prototype.name);
goog.exportProperty(ydn.db.events.StorageEvent.prototype, 'getVersion',
    ydn.db.events.StorageEvent.prototype.getVersion);
goog.exportProperty(ydn.db.events.StorageEvent.prototype, 'getOldVersion',
    ydn.db.events.StorageEvent.prototype.getOldVersion);
goog.exportProperty(ydn.db.events.StorageEvent.prototype, 'getOldSchema',
    ydn.db.events.StorageEvent.prototype.getOldSchema);
goog.exportProperty(ydn.db.events.StorageErrorEvent.prototype, 'getError',
    ydn.db.events.StorageErrorEvent.prototype.getError);

goog.exportProperty(ydn.db.Request.prototype, 'abort',
    ydn.db.Request.prototype.abort);
goog.exportProperty(ydn.db.Request.prototype, 'canAbort',
    ydn.db.Request.prototype.canAbort);
goog.exportProperty(ydn.db.Request.prototype, 'progress',
    ydn.db.Request.prototype.addProgback);
goog.exportProperty(ydn.db.Request.prototype, 'promise',
    ydn.db.Request.prototype.promise);




// Input 144
/**
 * @fileoverview Exports for ydn-db core module.
 *
 * Exporting variable are defined in separate namespace so that closure
 * project can use this library without exporting any of ydn-db properties.
 * Project that want to export ydn-db properties should require this namespace.
 */


goog.provide('ydn.db.core.exports');
goog.require('ydn.db.core.Storage');



goog.exportProperty(ydn.db.core.Storage.prototype, 'scan',
    ydn.db.core.Storage.prototype.scan);
goog.exportProperty(ydn.db.core.Storage.prototype, 'map',
    ydn.db.core.Storage.prototype.map);
goog.exportProperty(ydn.db.core.Storage.prototype, 'reduce',
    ydn.db.core.Storage.prototype.reduce);
goog.exportProperty(ydn.db.core.Storage.prototype, 'open',
    ydn.db.core.Storage.prototype.open);

goog.exportProperty(ydn.db.core.DbOperator.prototype, 'scan',
    ydn.db.core.DbOperator.prototype.scan);
goog.exportProperty(ydn.db.core.DbOperator.prototype, 'map',
    ydn.db.core.DbOperator.prototype.map);
goog.exportProperty(ydn.db.core.DbOperator.prototype, 'reduce',
    ydn.db.core.DbOperator.prototype.reduce);
goog.exportProperty(ydn.db.core.DbOperator.prototype, 'open',
    ydn.db.core.DbOperator.prototype.open);

goog.exportProperty(ydn.db.core.req.AbstractCursor.prototype, 'getKey',
    ydn.db.core.req.AbstractCursor.prototype.getKey);
goog.exportProperty(ydn.db.core.req.AbstractCursor.prototype, 'getPrimaryKey',
    ydn.db.core.req.AbstractCursor.prototype.getPrimaryKey);
goog.exportProperty(ydn.db.core.req.AbstractCursor.prototype, 'getValue',
    ydn.db.core.req.AbstractCursor.prototype.getValue);
goog.exportProperty(ydn.db.core.req.AbstractCursor.prototype, 'update',
    ydn.db.core.req.AbstractCursor.prototype.update);
goog.exportProperty(ydn.db.core.req.AbstractCursor.prototype, 'clear',
    ydn.db.core.req.AbstractCursor.prototype.clear);


goog.exportSymbol('ydn.db.Iterator', ydn.db.Iterator);
goog.exportSymbol('ydn.db.KeyIterator', ydn.db.KeyIterator);
goog.exportSymbol('ydn.db.ValueIterator', ydn.db.ValueIterator);
goog.exportSymbol('ydn.db.IndexIterator', ydn.db.IndexIterator);
goog.exportSymbol('ydn.db.IndexValueIterator', ydn.db.IndexValueIterator);


goog.exportProperty(ydn.db.Iterator.prototype, 'getState',
    ydn.db.Iterator.prototype.getState);
goog.exportProperty(ydn.db.Iterator.prototype, 'getKeyRange',
    ydn.db.Iterator.prototype.getKeyRange);
goog.exportProperty(ydn.db.Iterator.prototype, 'getIndexName',
    ydn.db.Iterator.prototype.getIndexName);
goog.exportProperty(ydn.db.Iterator.prototype, 'getStoreName',
    ydn.db.Iterator.prototype.getStoreName);
goog.exportProperty(ydn.db.Iterator.prototype, 'isReversed',
    ydn.db.Iterator.prototype.isReversed);
goog.exportProperty(ydn.db.Iterator.prototype, 'isUnique',
    ydn.db.Iterator.prototype.isUnique);
goog.exportProperty(ydn.db.Iterator.prototype, 'isKeyIterator',
    ydn.db.Iterator.prototype.isKeyIterator);
goog.exportProperty(ydn.db.Iterator.prototype, 'isIndexIterator',
    ydn.db.Iterator.prototype.isIndexIterator);
goog.exportProperty(ydn.db.Iterator.prototype, 'getPrimaryKey',
    ydn.db.Iterator.prototype.getPrimaryKey);
goog.exportProperty(ydn.db.Iterator.prototype, 'getKey',
    ydn.db.Iterator.prototype.getKey);
goog.exportProperty(ydn.db.Iterator.prototype, 'resume',
    ydn.db.Iterator.prototype.resume);
goog.exportProperty(ydn.db.Iterator.prototype, 'reset',
    ydn.db.Iterator.prototype.reset);
goog.exportProperty(ydn.db.Iterator.prototype, 'reverse',
    ydn.db.Iterator.prototype.reverse);

goog.exportProperty(ydn.db.KeyIterator, 'where', ydn.db.KeyIterator.where);
goog.exportProperty(ydn.db.ValueIterator, 'where', ydn.db.ValueIterator.where);
goog.exportProperty(ydn.db.IndexIterator, 'where', ydn.db.IndexIterator.where);
goog.exportProperty(ydn.db.IndexValueIterator, 'where',
    ydn.db.IndexValueIterator.where);

goog.exportSymbol('ydn.db.Streamer', ydn.db.Streamer);
goog.exportProperty(ydn.db.Streamer.prototype, 'push',
    ydn.db.Streamer.prototype.push);
goog.exportProperty(ydn.db.Streamer.prototype, 'collect',
    ydn.db.Streamer.prototype.collect);
goog.exportProperty(ydn.db.Streamer.prototype, 'setSink',
    ydn.db.Streamer.prototype.setSink);

// Input 145
/**
 * @fileoverview Exports for transaction module.
 *
 */

goog.provide('ydn.db.tr.exports');
goog.require('ydn.db.tr.Storage');
goog.require('ydn.db.tr.DbOperator');


goog.exportProperty(ydn.db.tr.Storage.prototype, 'branch',
    ydn.db.tr.Storage.prototype.branch);
goog.exportProperty(ydn.db.tr.Storage.prototype, 'getTxNo',
    ydn.db.tr.Storage.prototype.getTxNo);
goog.exportProperty(ydn.db.tr.DbOperator.prototype, 'getTxNo',
    ydn.db.tr.DbOperator.prototype.getTxNo);
goog.exportProperty(ydn.db.tr.Storage.prototype, 'run',
    ydn.db.tr.Storage.prototype.run);
goog.exportProperty(ydn.db.tr.Storage.prototype, 'spawn',
    ydn.db.tr.Storage.prototype.spawn);


// Input 146
/**
 * @fileoverview Exports for ydn-db crud module.
 *
 */

goog.provide('ydn.db.crud.exports');
goog.require('ydn.db.Key');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.crud.Storage');
goog.require('ydn.db.tr.exports');


goog.exportProperty(ydn.db.crud.Storage.prototype, 'branch',
    ydn.db.crud.Storage.prototype.branch);

goog.exportProperty(ydn.db.crud.Storage.prototype, 'add',
    ydn.db.crud.Storage.prototype.add);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'get',
    ydn.db.crud.Storage.prototype.get);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'keys',
    ydn.db.crud.Storage.prototype.keys);
//goog.exportProperty(ydn.db.crud.Storage.prototype, 'load',
//  ydn.db.crud.Storage.prototype.load);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'values',
    ydn.db.crud.Storage.prototype.values);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'put',
    ydn.db.crud.Storage.prototype.put);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'clear',
    ydn.db.crud.Storage.prototype.clear);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'remove',
    ydn.db.crud.Storage.prototype.remove);
goog.exportProperty(ydn.db.crud.Storage.prototype, 'count',
    ydn.db.crud.Storage.prototype.count);

goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'add',
    ydn.db.crud.DbOperator.prototype.add);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'get',
    ydn.db.crud.DbOperator.prototype.get);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'keys',
    ydn.db.crud.DbOperator.prototype.keys);
//goog.exportProperty(ydn.db.crud.Storage.prototype, 'load',
//  ydn.db.crud.Storage.prototype.load);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'values',
    ydn.db.crud.DbOperator.prototype.values);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'put',
    ydn.db.crud.DbOperator.prototype.put);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'clear',
    ydn.db.crud.DbOperator.prototype.clear);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'remove',
    ydn.db.crud.DbOperator.prototype.remove);
goog.exportProperty(ydn.db.crud.DbOperator.prototype, 'count',
    ydn.db.crud.DbOperator.prototype.count);


goog.exportSymbol('ydn.db.Key', ydn.db.Key);
goog.exportProperty(ydn.db.Key.prototype, 'id', ydn.db.Key.prototype.getId);
goog.exportProperty(ydn.db.Key.prototype, 'parent',
    ydn.db.Key.prototype.getParent);
goog.exportProperty(ydn.db.Key.prototype, 'storeName',
    ydn.db.Key.prototype.getStoreName);


goog.exportSymbol('ydn.db.KeyRange', ydn.db.KeyRange);
goog.exportProperty(ydn.db.KeyRange, 'upperBound', ydn.db.KeyRange.upperBound);
goog.exportProperty(ydn.db.KeyRange, 'lowerBound', ydn.db.KeyRange.lowerBound);
goog.exportProperty(ydn.db.KeyRange, 'bound', ydn.db.KeyRange.bound);
goog.exportProperty(ydn.db.KeyRange, 'only', ydn.db.KeyRange.only);
goog.exportProperty(ydn.db.KeyRange, 'starts', ydn.db.KeyRange.starts);


goog.exportProperty(ydn.db.events.Event.prototype, 'store_name',
    ydn.db.events.Event.prototype.store_name); // this don't work, why?
goog.exportProperty(ydn.db.events.Event.prototype, 'getStoreName',
    ydn.db.events.Event.prototype.getStoreName);

goog.exportProperty(ydn.db.events.RecordEvent.prototype, 'name',
    ydn.db.events.RecordEvent.prototype.name);
goog.exportProperty(ydn.db.events.RecordEvent.prototype, 'getKey',
    ydn.db.events.RecordEvent.prototype.getKey);
goog.exportProperty(ydn.db.events.RecordEvent.prototype, 'getValue',
    ydn.db.events.RecordEvent.prototype.getValue);


goog.exportProperty(ydn.db.events.StoreEvent.prototype, 'name',
    ydn.db.events.StoreEvent.prototype.name);
goog.exportProperty(ydn.db.events.StoreEvent.prototype, 'getKeys',
    ydn.db.events.StoreEvent.prototype.getKeys);
goog.exportProperty(ydn.db.events.StoreEvent.prototype, 'getValues',
    ydn.db.events.StoreEvent.prototype.getValues);



// Input 147
/**
 * @fileoverview Exports for ydn-db crud module.
 *
 */

goog.provide('ydn.db.sql.exports');
goog.require('ydn.db.sql.Storage');



goog.exportProperty(ydn.db.sql.Storage.prototype, 'executeSql',
    ydn.db.sql.Storage.prototype.executeSql);

goog.exportProperty(ydn.db.sql.DbOperator.prototype, 'executeSql',
    ydn.db.sql.DbOperator.prototype.executeSql);

//goog.exportSymbol('ydn.db.Storage', ydn.db.sql.Storage);

// Input 148
/**
 * @fileoverview Exports events.
 *
 */


goog.provide('ydn.db.tr.events.exports');
goog.require('ydn.db.tr.events');


goog.exportProperty(ydn.db.tr.Storage.prototype, 'addEventListener',
    ydn.db.tr.Storage.prototype.addEventListener);
goog.exportProperty(ydn.db.tr.Storage.prototype, 'removeEventListener',
    ydn.db.tr.Storage.prototype.removeEventListener);

// Input 149
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition the goog.debug.RelativeTimeProvider class.
 *
 */

goog.provide('goog.debug.RelativeTimeProvider');



/**
 * A simple object to keep track of a timestamp considered the start of
 * something. The main use is for the logger system to maintain a start time
 * that is occasionally reset. For example, in Gmail, we reset this relative
 * time at the start of a user action so that timings are offset from the
 * beginning of the action. This class also provides a singleton as the default
 * behavior for most use cases is to share the same start time.
 *
 * @constructor
 */
goog.debug.RelativeTimeProvider = function() {
  /**
   * The start time.
   * @type {number}
   * @private
   */
  this.relativeTimeStart_ = goog.now();
};


/**
 * Default instance.
 * @type {goog.debug.RelativeTimeProvider}
 * @private
 */
goog.debug.RelativeTimeProvider.defaultInstance_ =
    new goog.debug.RelativeTimeProvider();


/**
 * Sets the start time to the specified time.
 * @param {number} timeStamp The start time.
 */
goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
  this.relativeTimeStart_ = timeStamp;
};


/**
 * Resets the start time to now.
 */
goog.debug.RelativeTimeProvider.prototype.reset = function() {
  this.set(goog.now());
};


/**
 * @return {number} The start time.
 */
goog.debug.RelativeTimeProvider.prototype.get = function() {
  return this.relativeTimeStart_;
};


/**
 * @return {goog.debug.RelativeTimeProvider} The default instance.
 */
goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
  return goog.debug.RelativeTimeProvider.defaultInstance_;
};

// Input 150
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Definition of various formatters for logging. Please minimize
 * dependencies this file has on other closure classes as any dependency it
 * takes won't be able to use the logging infrastructure.
 *
 */

goog.provide('goog.debug.Formatter');
goog.provide('goog.debug.HtmlFormatter');
goog.provide('goog.debug.TextFormatter');

goog.require('goog.debug.RelativeTimeProvider');
goog.require('goog.string');



/**
 * Base class for Formatters. A Formatter is used to format a LogRecord into
 * something that can be displayed to the user.
 *
 * @param {string=} opt_prefix The prefix to place before text records.
 * @constructor
 */
goog.debug.Formatter = function(opt_prefix) {
  this.prefix_ = opt_prefix || '';

  /**
   * A provider that returns the relative start time.
   * @type {goog.debug.RelativeTimeProvider}
   * @private
   */
  this.startTimeProvider_ =
      goog.debug.RelativeTimeProvider.getDefaultInstance();
};


/**
 * Whether to show absolute time in the DebugWindow.
 * @type {boolean}
 */
goog.debug.Formatter.prototype.showAbsoluteTime = true;


/**
 * Whether to show relative time in the DebugWindow.
 * @type {boolean}
 */
goog.debug.Formatter.prototype.showRelativeTime = true;


/**
 * Whether to show the logger name in the DebugWindow.
 * @type {boolean}
 */
goog.debug.Formatter.prototype.showLoggerName = true;


/**
 * Whether to show the logger exception text.
 * @type {boolean}
 */
goog.debug.Formatter.prototype.showExceptionText = false;


/**
 * Whether to show the severity level.
 * @type {boolean}
 */
goog.debug.Formatter.prototype.showSeverityLevel = false;


/**
 * Formats a record.
 * @param {goog.debug.LogRecord} logRecord the logRecord to format.
 * @return {string} The formatted string.
 */
goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;


/**
 * Sets the start time provider. By default, this is the default instance
 * but can be changed.
 * @param {goog.debug.RelativeTimeProvider} provider The provider to use.
 */
goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
  this.startTimeProvider_ = provider;
};


/**
 * Returns the start time provider. By default, this is the default instance
 * but can be changed.
 * @return {goog.debug.RelativeTimeProvider} The start time provider.
 */
goog.debug.Formatter.prototype.getStartTimeProvider = function() {
  return this.startTimeProvider_;
};


/**
 * Resets the start relative time.
 */
goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
  this.startTimeProvider_.reset();
};


/**
 * Returns a string for the time/date of the LogRecord.
 * @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
 * @return {string} A string representation of the time/date of the LogRecord.
 * @private
 */
goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
  var time = new Date(logRecord.getMillis());
  return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
         goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
         goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
         goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
         goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
         goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
         goog.debug.Formatter.getTwoDigitString_(
             Math.floor(time.getMilliseconds() / 10));
};


/**
 * Returns the number as a two-digit string, meaning it prepends a 0 if the
 * number if less than 10.
 * @param {number} n The number to format.
 * @return {string} A two-digit string representation of {@code n}.
 * @private
 */
goog.debug.Formatter.getTwoDigitString_ = function(n) {
  if (n < 10) {
    return '0' + n;
  }
  return String(n);
};


/**
 * Returns a string for the number of seconds relative to the start time.
 * Prepads with spaces so that anything less than 1000 seconds takes up the
 * same number of characters for better formatting.
 * @param {goog.debug.LogRecord} logRecord The log to compare time to.
 * @param {number} relativeTimeStart The start time to compare to.
 * @return {string} The number of seconds of the LogRecord relative to the
 *     start time.
 * @private
 */
goog.debug.Formatter.getRelativeTime_ = function(logRecord,
                                                 relativeTimeStart) {
  var ms = logRecord.getMillis() - relativeTimeStart;
  var sec = ms / 1000;
  var str = sec.toFixed(3);

  var spacesToPrepend = 0;
  if (sec < 1) {
    spacesToPrepend = 2;
  } else {
    while (sec < 100) {
      spacesToPrepend++;
      sec *= 10;
    }
  }
  while (spacesToPrepend-- > 0) {
    str = ' ' + str;
  }
  return str;
};



/**
 * Formatter that returns formatted html. See formatRecord for the classes
 * it uses for various types of formatted output.
 *
 * @param {string=} opt_prefix The prefix to place before text records.
 * @constructor
 * @extends {goog.debug.Formatter}
 */
goog.debug.HtmlFormatter = function(opt_prefix) {
  goog.debug.Formatter.call(this, opt_prefix);
};
goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);


/**
 * Whether to show the logger exception text
 * @type {boolean}
 * @override
 */
goog.debug.HtmlFormatter.prototype.showExceptionText = true;


/**
 * Formats a record
 * @param {goog.debug.LogRecord} logRecord the logRecord to format.
 * @return {string} The formatted string as html.
 * @override
 */
goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
  var className;
  switch (logRecord.getLevel().value) {
    case goog.debug.Logger.Level.SHOUT.value:
      className = 'dbg-sh';
      break;
    case goog.debug.Logger.Level.SEVERE.value:
      className = 'dbg-sev';
      break;
    case goog.debug.Logger.Level.WARNING.value:
      className = 'dbg-w';
      break;
    case goog.debug.Logger.Level.INFO.value:
      className = 'dbg-i';
      break;
    case goog.debug.Logger.Level.FINE.value:
    default:
      className = 'dbg-f';
      break;
  }

  // Build message html
  var sb = [];
  sb.push(this.prefix_, ' ');
  if (this.showAbsoluteTime) {
    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
  }
  if (this.showRelativeTime) {
    sb.push('[',
        goog.string.whitespaceEscape(
            goog.debug.Formatter.getRelativeTime_(logRecord,
                this.startTimeProvider_.get())),
        's] ');
  }

  if (this.showLoggerName) {
    sb.push('[', goog.string.htmlEscape(logRecord.getLoggerName()), '] ');
  }
  if (this.showSeverityLevel) {
    sb.push('[', goog.string.htmlEscape(logRecord.getLevel().name), '] ');
  }
  sb.push('<span class="', className, '">',
      goog.string.newLineToBr(goog.string.whitespaceEscape(
          goog.string.htmlEscape(logRecord.getMessage()))));

  if (this.showExceptionText && logRecord.getException()) {
    sb.push('<br>',
        goog.string.newLineToBr(goog.string.whitespaceEscape(
            logRecord.getExceptionText() || '')));
  }
  sb.push('</span><br>');

  return sb.join('');
};



/**
 * Formatter that returns formatted plain text
 *
 * @param {string=} opt_prefix The prefix to place before text records.
 * @constructor
 * @extends {goog.debug.Formatter}
 */
goog.debug.TextFormatter = function(opt_prefix) {
  goog.debug.Formatter.call(this, opt_prefix);
};
goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);


/**
 * Formats a record as text
 * @param {goog.debug.LogRecord} logRecord the logRecord to format.
 * @return {string} The formatted string.
 * @override
 */
goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
  // Build message html
  var sb = [];
  sb.push(this.prefix_, ' ');
  if (this.showAbsoluteTime) {
    sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
  }
  if (this.showRelativeTime) {
    sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
        this.startTimeProvider_.get()), 's] ');
  }

  if (this.showLoggerName) {
    sb.push('[', logRecord.getLoggerName(), '] ');
  }
  if (this.showSeverityLevel) {
    sb.push('[', logRecord.getLevel().name, '] ');
  }
  sb.push(logRecord.getMessage(), '\n');
  if (this.showExceptionText && logRecord.getException()) {
    sb.push(logRecord.getExceptionText(), '\n');
  }
  return sb.join('');
};

// Input 151
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Simple logger that logs to the window console if available.
 *
 * Has an autoInstall option which can be put into initialization code, which
 * will start logging if "Debug=true" is in document.location.href
 *
 */

goog.provide('goog.debug.Console');

goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger.Level');
goog.require('goog.debug.TextFormatter');



/**
 * Create and install a log handler that logs to window.console if available
 * @constructor
 */
goog.debug.Console = function() {
  this.publishHandler_ = goog.bind(this.addLogRecord, this);

  /**
   * Formatter for formatted output.
   * @type {!goog.debug.TextFormatter}
   * @private
   */
  this.formatter_ = new goog.debug.TextFormatter();
  this.formatter_.showAbsoluteTime = false;
  this.formatter_.showExceptionText = false;

  this.isCapturing_ = false;
  this.logBuffer_ = '';

  /**
   * Loggers that we shouldn't output.
   * @type {!Object.<boolean>}
   * @private
   */
  this.filteredLoggers_ = {};
};


/**
 * Returns the text formatter used by this console
 * @return {!goog.debug.TextFormatter} The text formatter.
 */
goog.debug.Console.prototype.getFormatter = function() {
  return this.formatter_;
};


/**
 * Sets whether we are currently capturing logger output.
 * @param {boolean} capturing Whether to capture logger output.
 */
goog.debug.Console.prototype.setCapturing = function(capturing) {
  if (capturing == this.isCapturing_) {
    return;
  }

  // attach or detach handler from the root logger
  var rootLogger = goog.debug.LogManager.getRoot();
  if (capturing) {
    rootLogger.addHandler(this.publishHandler_);
  } else {
    rootLogger.removeHandler(this.publishHandler_);
    this.logBuffer = '';
  }
  this.isCapturing_ = capturing;
};


/**
 * Adds a log record.
 * @param {goog.debug.LogRecord} logRecord The log entry.
 */
goog.debug.Console.prototype.addLogRecord = function(logRecord) {

  // Check to see if the log record is filtered or not.
  if (this.filteredLoggers_[logRecord.getLoggerName()]) {
    return;
  }

  var record = this.formatter_.formatRecord(logRecord);
  var console = goog.debug.Console.console_;
  if (console) {
    switch (logRecord.getLevel()) {
      case goog.debug.Logger.Level.SHOUT:
        goog.debug.Console.logToConsole_(console, 'info', record);
        break;
      case goog.debug.Logger.Level.SEVERE:
        goog.debug.Console.logToConsole_(console, 'error', record);
        break;
      case goog.debug.Logger.Level.WARNING:
        goog.debug.Console.logToConsole_(console, 'warn', record);
        break;
      default:
        goog.debug.Console.logToConsole_(console, 'debug', record);
        break;
    }
  } else if (window.opera) {
    // window.opera.postError is considered an undefined property reference
    // by JSCompiler, so it has to be referenced using array notation instead.
    window.opera['postError'](record);
  } else {
    this.logBuffer_ += record;
  }
};


/**
 * Adds a logger name to be filtered.
 * @param {string} loggerName the logger name to add.
 */
goog.debug.Console.prototype.addFilter = function(loggerName) {
  this.filteredLoggers_[loggerName] = true;
};


/**
 * Removes a logger name to be filtered.
 * @param {string} loggerName the logger name to remove.
 */
goog.debug.Console.prototype.removeFilter = function(loggerName) {
  delete this.filteredLoggers_[loggerName];
};


/**
 * Global console logger instance
 * @type {goog.debug.Console}
 */
goog.debug.Console.instance = null;


/**
 * The console to which to log.  This is a property so it can be mocked out in
 * this unit test for goog.debug.Console.
 * @type {Object}
 * @private
 */
goog.debug.Console.console_ = window.console;


/**
 * Sets the console to which to log.
 * @param {!Object} console The console to which to log.
 */
goog.debug.Console.setConsole = function(console) {
  goog.debug.Console.console_ = console;
};


/**
 * Install the console and start capturing if "Debug=true" is in the page URL
 */
goog.debug.Console.autoInstall = function() {
  if (!goog.debug.Console.instance) {
    goog.debug.Console.instance = new goog.debug.Console();
  }

  if (window.location.href.indexOf('Debug=true') != -1) {
    goog.debug.Console.instance.setCapturing(true);
  }
};


/**
 * Show an alert with all of the captured debug information.
 * Information is only captured if console is not available
 */
goog.debug.Console.show = function() {
  alert(goog.debug.Console.instance.logBuffer_);
};


/**
 * Logs the record to the console using the given function.  If the function is
 * not available on the console object, the log function is used instead.
 * @param {!Object} console The console object.
 * @param {string} fnName The name of the function to use.
 * @param {string} record The record to log.
 * @private
 */
goog.debug.Console.logToConsole_ = function(console, fnName, record) {
  if (console[fnName]) {
    console[fnName](record);
  } else {
    console.log(record);
  }
};

// Input 152
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Browser capability checks for the dom package.
 *
 */


goog.provide('goog.dom.BrowserFeature');

goog.require('goog.userAgent');


/**
 * Enum of browser capabilities.
 * @enum {boolean}
 */
goog.dom.BrowserFeature = {
  /**
   * Whether attributes 'name' and 'type' can be added to an element after it's
   * created. False in Internet Explorer prior to version 9.
   */
  CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE ||
      goog.userAgent.isDocumentModeOrHigher(9),

  /**
   * Whether we can use element.children to access an element's Element
   * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
   * nodes in the collection.)
   */
  CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
      goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
      goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),

  /**
   * Opera, Safari 3, and Internet Explorer 9 all support innerText but they
   * include text nodes in script and style tags. Not document-mode-dependent.
   */
  CAN_USE_INNER_TEXT: (
      goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),

  /**
   * MSIE, Opera, and Safari>=4 support element.parentElement to access an
   * element's parent if it is an Element.
   */
  CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA ||
      goog.userAgent.WEBKIT,

  /**
   * Whether NoScope elements need a scoped element written before them in
   * innerHTML.
   * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
   */
  INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE
};

// Input 153
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Defines the goog.dom.TagName enum.  This enumerates
 * all HTML tag names specified in either the the W3C HTML 4.01 index of
 * elements or the HTML5 draft specification.
 *
 * References:
 * http://www.w3.org/TR/html401/index/elements.html
 * http://dev.w3.org/html5/spec/section-index.html
 *
 */
goog.provide('goog.dom.TagName');


/**
 * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
 * specifications.
 * @enum {string}
 */
goog.dom.TagName = {
  A: 'A',
  ABBR: 'ABBR',
  ACRONYM: 'ACRONYM',
  ADDRESS: 'ADDRESS',
  APPLET: 'APPLET',
  AREA: 'AREA',
  ARTICLE: 'ARTICLE',
  ASIDE: 'ASIDE',
  AUDIO: 'AUDIO',
  B: 'B',
  BASE: 'BASE',
  BASEFONT: 'BASEFONT',
  BDI: 'BDI',
  BDO: 'BDO',
  BIG: 'BIG',
  BLOCKQUOTE: 'BLOCKQUOTE',
  BODY: 'BODY',
  BR: 'BR',
  BUTTON: 'BUTTON',
  CANVAS: 'CANVAS',
  CAPTION: 'CAPTION',
  CENTER: 'CENTER',
  CITE: 'CITE',
  CODE: 'CODE',
  COL: 'COL',
  COLGROUP: 'COLGROUP',
  COMMAND: 'COMMAND',
  DATA: 'DATA',
  DATALIST: 'DATALIST',
  DD: 'DD',
  DEL: 'DEL',
  DETAILS: 'DETAILS',
  DFN: 'DFN',
  DIALOG: 'DIALOG',
  DIR: 'DIR',
  DIV: 'DIV',
  DL: 'DL',
  DT: 'DT',
  EM: 'EM',
  EMBED: 'EMBED',
  FIELDSET: 'FIELDSET',
  FIGCAPTION: 'FIGCAPTION',
  FIGURE: 'FIGURE',
  FONT: 'FONT',
  FOOTER: 'FOOTER',
  FORM: 'FORM',
  FRAME: 'FRAME',
  FRAMESET: 'FRAMESET',
  H1: 'H1',
  H2: 'H2',
  H3: 'H3',
  H4: 'H4',
  H5: 'H5',
  H6: 'H6',
  HEAD: 'HEAD',
  HEADER: 'HEADER',
  HGROUP: 'HGROUP',
  HR: 'HR',
  HTML: 'HTML',
  I: 'I',
  IFRAME: 'IFRAME',
  IMG: 'IMG',
  INPUT: 'INPUT',
  INS: 'INS',
  ISINDEX: 'ISINDEX',
  KBD: 'KBD',
  KEYGEN: 'KEYGEN',
  LABEL: 'LABEL',
  LEGEND: 'LEGEND',
  LI: 'LI',
  LINK: 'LINK',
  MAP: 'MAP',
  MARK: 'MARK',
  MATH: 'MATH',
  MENU: 'MENU',
  META: 'META',
  METER: 'METER',
  NAV: 'NAV',
  NOFRAMES: 'NOFRAMES',
  NOSCRIPT: 'NOSCRIPT',
  OBJECT: 'OBJECT',
  OL: 'OL',
  OPTGROUP: 'OPTGROUP',
  OPTION: 'OPTION',
  OUTPUT: 'OUTPUT',
  P: 'P',
  PARAM: 'PARAM',
  PRE: 'PRE',
  PROGRESS: 'PROGRESS',
  Q: 'Q',
  RP: 'RP',
  RT: 'RT',
  RUBY: 'RUBY',
  S: 'S',
  SAMP: 'SAMP',
  SCRIPT: 'SCRIPT',
  SECTION: 'SECTION',
  SELECT: 'SELECT',
  SMALL: 'SMALL',
  SOURCE: 'SOURCE',
  SPAN: 'SPAN',
  STRIKE: 'STRIKE',
  STRONG: 'STRONG',
  STYLE: 'STYLE',
  SUB: 'SUB',
  SUMMARY: 'SUMMARY',
  SUP: 'SUP',
  SVG: 'SVG',
  TABLE: 'TABLE',
  TBODY: 'TBODY',
  TD: 'TD',
  TEXTAREA: 'TEXTAREA',
  TFOOT: 'TFOOT',
  TH: 'TH',
  THEAD: 'THEAD',
  TIME: 'TIME',
  TITLE: 'TITLE',
  TR: 'TR',
  TRACK: 'TRACK',
  TT: 'TT',
  U: 'U',
  UL: 'UL',
  VAR: 'VAR',
  VIDEO: 'VIDEO',
  WBR: 'WBR'
};

// Input 154
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for adding, removing and setting classes.  Prefer
 * {@link goog.dom.classlist} over these utilities since goog.dom.classlist
 * conforms closer to the semantics of Element.classList, is faster (uses
 * native methods rather than parsing strings on every call) and compiles
 * to smaller code as a result.
 *
 * Note: these utilities are meant to operate on HTMLElements and
 * will not work on elements with differing interfaces (such as SVGElements).
 *
 */


goog.provide('goog.dom.classes');

goog.require('goog.array');


/**
 * Sets the entire class name of an element.
 * @param {Node} element DOM node to set class of.
 * @param {string} className Class name(s) to apply to element.
 */
goog.dom.classes.set = function(element, className) {
  element.className = className;
};


/**
 * Gets an array of class names on an element
 * @param {Node} element DOM node to get class of.
 * @return {!Array} Class names on {@code element}. Some browsers add extra
 *     properties to the array. Do not depend on any of these!
 */
goog.dom.classes.get = function(element) {
  var className = element.className;
  // Some types of elements don't have a className in IE (e.g. iframes).
  // Furthermore, in Firefox, className is not a string when the element is
  // an SVG element.
  return goog.isString(className) && className.match(/\S+/g) || [];
};


/**
 * Adds a class or classes to an element. Does not add multiples of class names.
 * @param {Node} element DOM node to add class to.
 * @param {...string} var_args Class names to add.
 * @return {boolean} Whether class was added (or all classes were added).
 */
goog.dom.classes.add = function(element, var_args) {
  var classes = goog.dom.classes.get(element);
  var args = goog.array.slice(arguments, 1);
  var expectedCount = classes.length + args.length;
  goog.dom.classes.add_(classes, args);
  goog.dom.classes.set(element, classes.join(' '));
  return classes.length == expectedCount;
};


/**
 * Removes a class or classes from an element.
 * @param {Node} element DOM node to remove class from.
 * @param {...string} var_args Class name(s) to remove.
 * @return {boolean} Whether all classes in {@code var_args} were found and
 *     removed.
 */
goog.dom.classes.remove = function(element, var_args) {
  var classes = goog.dom.classes.get(element);
  var args = goog.array.slice(arguments, 1);
  var newClasses = goog.dom.classes.getDifference_(classes, args);
  goog.dom.classes.set(element, newClasses.join(' '));
  return newClasses.length == classes.length - args.length;
};


/**
 * Helper method for {@link goog.dom.classes.add} and
 * {@link goog.dom.classes.addRemove}. Adds one or more classes to the supplied
 * classes array.
 * @param {Array.<string>} classes All class names for the element, will be
 *     updated to have the classes supplied in {@code args} added.
 * @param {Array.<string>} args Class names to add.
 * @private
 */
goog.dom.classes.add_ = function(classes, args) {
  for (var i = 0; i < args.length; i++) {
    if (!goog.array.contains(classes, args[i])) {
      classes.push(args[i]);
    }
  }
};


/**
 * Helper method for {@link goog.dom.classes.remove} and
 * {@link goog.dom.classes.addRemove}. Calculates the difference of two arrays.
 * @param {!Array.<string>} arr1 First array.
 * @param {!Array.<string>} arr2 Second array.
 * @return {!Array.<string>} The first array without the elements of the second
 *     array.
 * @private
 */
goog.dom.classes.getDifference_ = function(arr1, arr2) {
  return goog.array.filter(arr1, function(item) {
    return !goog.array.contains(arr2, item);
  });
};


/**
 * Switches a class on an element from one to another without disturbing other
 * classes. If the fromClass isn't removed, the toClass won't be added.
 * @param {Node} element DOM node to swap classes on.
 * @param {string} fromClass Class to remove.
 * @param {string} toClass Class to add.
 * @return {boolean} Whether classes were switched.
 */
goog.dom.classes.swap = function(element, fromClass, toClass) {
  var classes = goog.dom.classes.get(element);

  var removed = false;
  for (var i = 0; i < classes.length; i++) {
    if (classes[i] == fromClass) {
      goog.array.splice(classes, i--, 1);
      removed = true;
    }
  }

  if (removed) {
    classes.push(toClass);
    goog.dom.classes.set(element, classes.join(' '));
  }

  return removed;
};


/**
 * Adds zero or more classes to an element and removes zero or more as a single
 * operation. Unlike calling {@link goog.dom.classes.add} and
 * {@link goog.dom.classes.remove} separately, this is more efficient as it only
 * parses the class property once.
 *
 * If a class is in both the remove and add lists, it will be added. Thus,
 * you can use this instead of {@link goog.dom.classes.swap} when you have
 * more than two class names that you want to swap.
 *
 * @param {Node} element DOM node to swap classes on.
 * @param {?(string|Array.<string>)} classesToRemove Class or classes to
 *     remove, if null no classes are removed.
 * @param {?(string|Array.<string>)} classesToAdd Class or classes to add, if
 *     null no classes are added.
 */
goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) {
  var classes = goog.dom.classes.get(element);
  if (goog.isString(classesToRemove)) {
    goog.array.remove(classes, classesToRemove);
  } else if (goog.isArray(classesToRemove)) {
    classes = goog.dom.classes.getDifference_(classes, classesToRemove);
  }

  if (goog.isString(classesToAdd) &&
      !goog.array.contains(classes, classesToAdd)) {
    classes.push(classesToAdd);
  } else if (goog.isArray(classesToAdd)) {
    goog.dom.classes.add_(classes, classesToAdd);
  }

  goog.dom.classes.set(element, classes.join(' '));
};


/**
 * Returns true if an element has a class.
 * @param {Node} element DOM node to test.
 * @param {string} className Class name to test for.
 * @return {boolean} Whether element has the class.
 */
goog.dom.classes.has = function(element, className) {
  return goog.array.contains(goog.dom.classes.get(element), className);
};


/**
 * Adds or removes a class depending on the enabled argument.
 * @param {Node} element DOM node to add or remove the class on.
 * @param {string} className Class name to add or remove.
 * @param {boolean} enabled Whether to add or remove the class (true adds,
 *     false removes).
 */
goog.dom.classes.enable = function(element, className, enabled) {
  if (enabled) {
    goog.dom.classes.add(element, className);
  } else {
    goog.dom.classes.remove(element, className);
  }
};


/**
 * Removes a class if an element has it, and adds it the element doesn't have
 * it.  Won't affect other classes on the node.
 * @param {Node} element DOM node to toggle class on.
 * @param {string} className Class to toggle.
 * @return {boolean} True if class was added, false if it was removed
 *     (in other words, whether element has the class after this function has
 *     been called).
 */
goog.dom.classes.toggle = function(element, className) {
  var add = !goog.dom.classes.has(element, className);
  goog.dom.classes.enable(element, className, add);
  return add;
};

// Input 155
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Additional mathematical functions.
 */

goog.provide('goog.math');

goog.require('goog.array');
goog.require('goog.asserts');


/**
 * Returns a random integer greater than or equal to 0 and less than {@code a}.
 * @param {number} a  The upper bound for the random integer (exclusive).
 * @return {number} A random integer N such that 0 <= N < a.
 */
goog.math.randomInt = function(a) {
  return Math.floor(Math.random() * a);
};


/**
 * Returns a random number greater than or equal to {@code a} and less than
 * {@code b}.
 * @param {number} a  The lower bound for the random number (inclusive).
 * @param {number} b  The upper bound for the random number (exclusive).
 * @return {number} A random number N such that a <= N < b.
 */
goog.math.uniformRandom = function(a, b) {
  return a + Math.random() * (b - a);
};


/**
 * Takes a number and clamps it to within the provided bounds.
 * @param {number} value The input number.
 * @param {number} min The minimum value to return.
 * @param {number} max The maximum value to return.
 * @return {number} The input number if it is within bounds, or the nearest
 *     number within the bounds.
 */
goog.math.clamp = function(value, min, max) {
  return Math.min(Math.max(value, min), max);
};


/**
 * The % operator in JavaScript returns the remainder of a / b, but differs from
 * some other languages in that the result will have the same sign as the
 * dividend. For example, -1 % 8 == -1, whereas in some other languages
 * (such as Python) the result would be 7. This function emulates the more
 * correct modulo behavior, which is useful for certain applications such as
 * calculating an offset index in a circular list.
 *
 * @param {number} a The dividend.
 * @param {number} b The divisor.
 * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
 *     or b < x <= 0, depending on the sign of b).
 */
goog.math.modulo = function(a, b) {
  var r = a % b;
  // If r and b differ in sign, add b to wrap the result to the correct sign.
  return (r * b < 0) ? r + b : r;
};


/**
 * Performs linear interpolation between values a and b. Returns the value
 * between a and b proportional to x (when x is between 0 and 1. When x is
 * outside this range, the return value is a linear extrapolation).
 * @param {number} a A number.
 * @param {number} b A number.
 * @param {number} x The proportion between a and b.
 * @return {number} The interpolated value between a and b.
 */
goog.math.lerp = function(a, b, x) {
  return a + x * (b - a);
};


/**
 * Tests whether the two values are equal to each other, within a certain
 * tolerance to adjust for floating pount errors.
 * @param {number} a A number.
 * @param {number} b A number.
 * @param {number=} opt_tolerance Optional tolerance range. Defaults
 *     to 0.000001. If specified, should be greater than 0.
 * @return {boolean} Whether {@code a} and {@code b} are nearly equal.
 */
goog.math.nearlyEquals = function(a, b, opt_tolerance) {
  return Math.abs(a - b) <= (opt_tolerance || 0.000001);
};


/**
 * Standardizes an angle to be in range [0-360). Negative angles become
 * positive, and values greater than 360 are returned modulo 360.
 * @param {number} angle Angle in degrees.
 * @return {number} Standardized angle.
 */
goog.math.standardAngle = function(angle) {
  return goog.math.modulo(angle, 360);
};


/**
 * Converts degrees to radians.
 * @param {number} angleDegrees Angle in degrees.
 * @return {number} Angle in radians.
 */
goog.math.toRadians = function(angleDegrees) {
  return angleDegrees * Math.PI / 180;
};


/**
 * Converts radians to degrees.
 * @param {number} angleRadians Angle in radians.
 * @return {number} Angle in degrees.
 */
goog.math.toDegrees = function(angleRadians) {
  return angleRadians * 180 / Math.PI;
};


/**
 * For a given angle and radius, finds the X portion of the offset.
 * @param {number} degrees Angle in degrees (zero points in +X direction).
 * @param {number} radius Radius.
 * @return {number} The x-distance for the angle and radius.
 */
goog.math.angleDx = function(degrees, radius) {
  return radius * Math.cos(goog.math.toRadians(degrees));
};


/**
 * For a given angle and radius, finds the Y portion of the offset.
 * @param {number} degrees Angle in degrees (zero points in +X direction).
 * @param {number} radius Radius.
 * @return {number} The y-distance for the angle and radius.
 */
goog.math.angleDy = function(degrees, radius) {
  return radius * Math.sin(goog.math.toRadians(degrees));
};


/**
 * Computes the angle between two points (x1,y1) and (x2,y2).
 * Angle zero points in the +X direction, 90 degrees points in the +Y
 * direction (down) and from there we grow clockwise towards 360 degrees.
 * @param {number} x1 x of first point.
 * @param {number} y1 y of first point.
 * @param {number} x2 x of second point.
 * @param {number} y2 y of second point.
 * @return {number} Standardized angle in degrees of the vector from
 *     x1,y1 to x2,y2.
 */
goog.math.angle = function(x1, y1, x2, y2) {
  return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
                                                                x2 - x1)));
};


/**
 * Computes the difference between startAngle and endAngle (angles in degrees).
 * @param {number} startAngle  Start angle in degrees.
 * @param {number} endAngle  End angle in degrees.
 * @return {number} The number of degrees that when added to
 *     startAngle will result in endAngle. Positive numbers mean that the
 *     direction is clockwise. Negative numbers indicate a counter-clockwise
 *     direction.
 *     The shortest route (clockwise vs counter-clockwise) between the angles
 *     is used.
 *     When the difference is 180 degrees, the function returns 180 (not -180)
 *     angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
 *     angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
 */
goog.math.angleDifference = function(startAngle, endAngle) {
  var d = goog.math.standardAngle(endAngle) -
          goog.math.standardAngle(startAngle);
  if (d > 180) {
    d = d - 360;
  } else if (d <= -180) {
    d = 360 + d;
  }
  return d;
};


/**
 * Returns the sign of a number as per the "sign" or "signum" function.
 * @param {number} x The number to take the sign of.
 * @return {number} -1 when negative, 1 when positive, 0 when 0.
 */
goog.math.sign = function(x) {
  return x == 0 ? 0 : (x < 0 ? -1 : 1);
};


/**
 * JavaScript implementation of Longest Common Subsequence problem.
 * http://en.wikipedia.org/wiki/Longest_common_subsequence
 *
 * Returns the longest possible array that is subarray of both of given arrays.
 *
 * @param {Array.<Object>} array1 First array of objects.
 * @param {Array.<Object>} array2 Second array of objects.
 * @param {Function=} opt_compareFn Function that acts as a custom comparator
 *     for the array ojects. Function should return true if objects are equal,
 *     otherwise false.
 * @param {Function=} opt_collectorFn Function used to decide what to return
 *     as a result subsequence. It accepts 2 arguments: index of common element
 *     in the first array and index in the second. The default function returns
 *     element from the first array.
 * @return {Array.<Object>} A list of objects that are common to both arrays
 *     such that there is no common subsequence with size greater than the
 *     length of the list.
 */
goog.math.longestCommonSubsequence = function(
    array1, array2, opt_compareFn, opt_collectorFn) {

  var compare = opt_compareFn || function(a, b) {
    return a == b;
  };

  var collect = opt_collectorFn || function(i1, i2) {
    return array1[i1];
  };

  var length1 = array1.length;
  var length2 = array2.length;

  var arr = [];
  for (var i = 0; i < length1 + 1; i++) {
    arr[i] = [];
    arr[i][0] = 0;
  }

  for (var j = 0; j < length2 + 1; j++) {
    arr[0][j] = 0;
  }

  for (i = 1; i <= length1; i++) {
    for (j = 1; j <= length1; j++) {
      if (compare(array1[i - 1], array2[j - 1])) {
        arr[i][j] = arr[i - 1][j - 1] + 1;
      } else {
        arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
      }
    }
  }

  // Backtracking
  var result = [];
  var i = length1, j = length2;
  while (i > 0 && j > 0) {
    if (compare(array1[i - 1], array2[j - 1])) {
      result.unshift(collect(i - 1, j - 1));
      i--;
      j--;
    } else {
      if (arr[i - 1][j] > arr[i][j - 1]) {
        i--;
      } else {
        j--;
      }
    }
  }

  return result;
};


/**
 * Returns the sum of the arguments.
 * @param {...number} var_args Numbers to add.
 * @return {number} The sum of the arguments (0 if no arguments were provided,
 *     {@code NaN} if any of the arguments is not a valid number).
 */
goog.math.sum = function(var_args) {
  return /** @type {number} */ (goog.array.reduce(arguments,
      function(sum, value) {
        return sum + value;
      }, 0));
};


/**
 * Returns the arithmetic mean of the arguments.
 * @param {...number} var_args Numbers to average.
 * @return {number} The average of the arguments ({@code NaN} if no arguments
 *     were provided or any of the arguments is not a valid number).
 */
goog.math.average = function(var_args) {
  return goog.math.sum.apply(null, arguments) / arguments.length;
};


/**
 * Returns the sample standard deviation of the arguments.  For a definition of
 * sample standard deviation, see e.g.
 * http://en.wikipedia.org/wiki/Standard_deviation
 * @param {...number} var_args Number samples to analyze.
 * @return {number} The sample standard deviation of the arguments (0 if fewer
 *     than two samples were provided, or {@code NaN} if any of the samples is
 *     not a valid number).
 */
goog.math.standardDeviation = function(var_args) {
  var sampleSize = arguments.length;
  if (sampleSize < 2) {
    return 0;
  }

  var mean = goog.math.average.apply(null, arguments);
  var variance = goog.math.sum.apply(null, goog.array.map(arguments,
      function(val) {
        return Math.pow(val - mean, 2);
      })) / (sampleSize - 1);

  return Math.sqrt(variance);
};


/**
 * Returns whether the supplied number represents an integer, i.e. that is has
 * no fractional component.  No range-checking is performed on the number.
 * @param {number} num The number to test.
 * @return {boolean} Whether {@code num} is an integer.
 */
goog.math.isInt = function(num) {
  return isFinite(num) && num % 1 == 0;
};


/**
 * Returns whether the supplied number is finite and not NaN.
 * @param {number} num The number to test.
 * @return {boolean} Whether {@code num} is a finite number.
 */
goog.math.isFiniteNumber = function(num) {
  return isFinite(num) && !isNaN(num);
};


/**
 * A tweaked variant of {@code Math.floor} which tolerates if the passed number
 * is infinitesimally smaller than the closest integer. It often happens with
 * the results of floating point calculations because of the finite precision
 * of the intermediate results. For example {@code Math.floor(Math.log(1000) /
 * Math.LN10) == 2}, not 3 as one would expect.
 * @param {number} num A number.
 * @param {number=} opt_epsilon An infinitesimally small positive number, the
 *     rounding error to tolerate.
 * @return {number} The largest integer less than or equal to {@code num}.
 */
goog.math.safeFloor = function(num, opt_epsilon) {
  goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
  return Math.floor(num + (opt_epsilon || 2e-15));
};


/**
 * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
 * details.
 * @param {number} num A number.
 * @param {number=} opt_epsilon An infinitesimally small positive number, the
 *     rounding error to tolerate.
 * @return {number} The smallest integer greater than or equal to {@code num}.
 */
goog.math.safeCeil = function(num, opt_epsilon) {
  goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
  return Math.ceil(num - (opt_epsilon || 2e-15));
};

// Input 156
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A utility class for representing two-dimensional positions.
 */


goog.provide('goog.math.Coordinate');

goog.require('goog.math');



/**
 * Class for representing coordinates and positions.
 * @param {number=} opt_x Left, defaults to 0.
 * @param {number=} opt_y Top, defaults to 0.
 * @constructor
 */
goog.math.Coordinate = function(opt_x, opt_y) {
  /**
   * X-value
   * @type {number}
   */
  this.x = goog.isDef(opt_x) ? opt_x : 0;

  /**
   * Y-value
   * @type {number}
   */
  this.y = goog.isDef(opt_y) ? opt_y : 0;
};


/**
 * Returns a new copy of the coordinate.
 * @return {!goog.math.Coordinate} A clone of this coordinate.
 */
goog.math.Coordinate.prototype.clone = function() {
  return new goog.math.Coordinate(this.x, this.y);
};


if (goog.DEBUG) {
  /**
   * Returns a nice string representing the coordinate.
   * @return {string} In the form (50, 73).
   * @override
   */
  goog.math.Coordinate.prototype.toString = function() {
    return '(' + this.x + ', ' + this.y + ')';
  };
}


/**
 * Compares coordinates for equality.
 * @param {goog.math.Coordinate} a A Coordinate.
 * @param {goog.math.Coordinate} b A Coordinate.
 * @return {boolean} True iff the coordinates are equal, or if both are null.
 */
goog.math.Coordinate.equals = function(a, b) {
  if (a == b) {
    return true;
  }
  if (!a || !b) {
    return false;
  }
  return a.x == b.x && a.y == b.y;
};


/**
 * Returns the distance between two coordinates.
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @param {!goog.math.Coordinate} b A Coordinate.
 * @return {number} The distance between {@code a} and {@code b}.
 */
goog.math.Coordinate.distance = function(a, b) {
  var dx = a.x - b.x;
  var dy = a.y - b.y;
  return Math.sqrt(dx * dx + dy * dy);
};


/**
 * Returns the magnitude of a coordinate.
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @return {number} The distance between the origin and {@code a}.
 */
goog.math.Coordinate.magnitude = function(a) {
  return Math.sqrt(a.x * a.x + a.y * a.y);
};


/**
 * Returns the angle from the origin to a coordinate.
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @return {number} The angle, in degrees, clockwise from the positive X
 *     axis to {@code a}.
 */
goog.math.Coordinate.azimuth = function(a) {
  return goog.math.angle(0, 0, a.x, a.y);
};


/**
 * Returns the squared distance between two coordinates. Squared distances can
 * be used for comparisons when the actual value is not required.
 *
 * Performance note: eliminating the square root is an optimization often used
 * in lower-level languages, but the speed difference is not nearly as
 * pronounced in JavaScript (only a few percent.)
 *
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @param {!goog.math.Coordinate} b A Coordinate.
 * @return {number} The squared distance between {@code a} and {@code b}.
 */
goog.math.Coordinate.squaredDistance = function(a, b) {
  var dx = a.x - b.x;
  var dy = a.y - b.y;
  return dx * dx + dy * dy;
};


/**
 * Returns the difference between two coordinates as a new
 * goog.math.Coordinate.
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @param {!goog.math.Coordinate} b A Coordinate.
 * @return {!goog.math.Coordinate} A Coordinate representing the difference
 *     between {@code a} and {@code b}.
 */
goog.math.Coordinate.difference = function(a, b) {
  return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
};


/**
 * Returns the sum of two coordinates as a new goog.math.Coordinate.
 * @param {!goog.math.Coordinate} a A Coordinate.
 * @param {!goog.math.Coordinate} b A Coordinate.
 * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
 *     coordinates.
 */
goog.math.Coordinate.sum = function(a, b) {
  return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
};


/**
 * Rounds the x and y fields to the next larger integer values.
 * @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
 */
goog.math.Coordinate.prototype.ceil = function() {
  this.x = Math.ceil(this.x);
  this.y = Math.ceil(this.y);
  return this;
};


/**
 * Rounds the x and y fields to the next smaller integer values.
 * @return {!goog.math.Coordinate} This coordinate with floored fields.
 */
goog.math.Coordinate.prototype.floor = function() {
  this.x = Math.floor(this.x);
  this.y = Math.floor(this.y);
  return this;
};


/**
 * Rounds the x and y fields to the nearest integer values.
 * @return {!goog.math.Coordinate} This coordinate with rounded fields.
 */
goog.math.Coordinate.prototype.round = function() {
  this.x = Math.round(this.x);
  this.y = Math.round(this.y);
  return this;
};


/**
 * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
 * is given, then the x and y values are translated by the coordinate's x and y.
 * Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
 * respectively.
 * @param {number|goog.math.Coordinate} tx The value to translate x by or the
 *     the coordinate to translate this coordinate by.
 * @param {number=} opt_ty The value to translate y by.
 * @return {!goog.math.Coordinate} This coordinate after translating.
 */
goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
  if (tx instanceof goog.math.Coordinate) {
    this.x += tx.x;
    this.y += tx.y;
  } else {
    this.x += tx;
    if (goog.isNumber(opt_ty)) {
      this.y += opt_ty;
    }
  }
  return this;
};


/**
 * Scales this coordinate by the given scale factors. The x and y values are
 * scaled by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy}
 * is not given, then {@code sx} is used for both x and y.
 * @param {number} sx The scale factor to use for the x dimension.
 * @param {number=} opt_sy The scale factor to use for the y dimension.
 * @return {!goog.math.Coordinate} This coordinate after scaling.
 */
goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
  this.x *= sx;
  this.y *= sy;
  return this;
};

// Input 157
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A utility class for representing two-dimensional sizes.
 */


goog.provide('goog.math.Size');



/**
 * Class for representing sizes consisting of a width and height. Undefined
 * width and height support is deprecated and results in compiler warning.
 * @param {number} width Width.
 * @param {number} height Height.
 * @constructor
 */
goog.math.Size = function(width, height) {
  /**
   * Width
   * @type {number}
   */
  this.width = width;

  /**
   * Height
   * @type {number}
   */
  this.height = height;
};


/**
 * Compares sizes for equality.
 * @param {goog.math.Size} a A Size.
 * @param {goog.math.Size} b A Size.
 * @return {boolean} True iff the sizes have equal widths and equal
 *     heights, or if both are null.
 */
goog.math.Size.equals = function(a, b) {
  if (a == b) {
    return true;
  }
  if (!a || !b) {
    return false;
  }
  return a.width == b.width && a.height == b.height;
};


/**
 * @return {!goog.math.Size} A new copy of the Size.
 */
goog.math.Size.prototype.clone = function() {
  return new goog.math.Size(this.width, this.height);
};


if (goog.DEBUG) {
  /**
   * Returns a nice string representing size.
   * @return {string} In the form (50 x 73).
   * @override
   */
  goog.math.Size.prototype.toString = function() {
    return '(' + this.width + ' x ' + this.height + ')';
  };
}


/**
 * @return {number} The longer of the two dimensions in the size.
 */
goog.math.Size.prototype.getLongest = function() {
  return Math.max(this.width, this.height);
};


/**
 * @return {number} The shorter of the two dimensions in the size.
 */
goog.math.Size.prototype.getShortest = function() {
  return Math.min(this.width, this.height);
};


/**
 * @return {number} The area of the size (width * height).
 */
goog.math.Size.prototype.area = function() {
  return this.width * this.height;
};


/**
 * @return {number} The perimeter of the size (width + height) * 2.
 */
goog.math.Size.prototype.perimeter = function() {
  return (this.width + this.height) * 2;
};


/**
 * @return {number} The ratio of the size's width to its height.
 */
goog.math.Size.prototype.aspectRatio = function() {
  return this.width / this.height;
};


/**
 * @return {boolean} True if the size has zero area, false if both dimensions
 *     are non-zero numbers.
 */
goog.math.Size.prototype.isEmpty = function() {
  return !this.area();
};


/**
 * Clamps the width and height parameters upward to integer values.
 * @return {!goog.math.Size} This size with ceil'd components.
 */
goog.math.Size.prototype.ceil = function() {
  this.width = Math.ceil(this.width);
  this.height = Math.ceil(this.height);
  return this;
};


/**
 * @param {!goog.math.Size} target The target size.
 * @return {boolean} True if this Size is the same size or smaller than the
 *     target size in both dimensions.
 */
goog.math.Size.prototype.fitsInside = function(target) {
  return this.width <= target.width && this.height <= target.height;
};


/**
 * Clamps the width and height parameters downward to integer values.
 * @return {!goog.math.Size} This size with floored components.
 */
goog.math.Size.prototype.floor = function() {
  this.width = Math.floor(this.width);
  this.height = Math.floor(this.height);
  return this;
};


/**
 * Rounds the width and height parameters to integer values.
 * @return {!goog.math.Size} This size with rounded components.
 */
goog.math.Size.prototype.round = function() {
  this.width = Math.round(this.width);
  this.height = Math.round(this.height);
  return this;
};


/**
 * Scales this size by the given scale factors. The width and height are scaled
 * by {@code sx} and {@code opt_sy} respectively.  If {@code opt_sy} is not
 * given, then {@code sx} is used for both the width and height.
 * @param {number} sx The scale factor to use for the width.
 * @param {number=} opt_sy The scale factor to use for the height.
 * @return {!goog.math.Size} This Size object after scaling.
 */
goog.math.Size.prototype.scale = function(sx, opt_sy) {
  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
  this.width *= sx;
  this.height *= sy;
  return this;
};


/**
 * Uniformly scales the size to fit inside the dimensions of a given size. The
 * original aspect ratio will be preserved.
 *
 * This function assumes that both Sizes contain strictly positive dimensions.
 * @param {!goog.math.Size} target The target size.
 * @return {!goog.math.Size} This Size object, after optional scaling.
 */
goog.math.Size.prototype.scaleToFit = function(target) {
  var s = this.aspectRatio() > target.aspectRatio() ?
      target.width / this.width :
      target.height / this.height;

  return this.scale(s);
};

// Input 158
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for manipulating the browser's Document Object Model
 * Inspiration taken *heavily* from mochikit (http://mochikit.com/).
 *
 * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
 * to a different document object.  This is useful if you are working with
 * frames or multiple windows.
 *
 */


// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
// is that getTextContent should mimic the DOM3 textContent. We should add a
// getInnerText (or getText) which tries to return the visible text, innerText.


goog.provide('goog.dom');
goog.provide('goog.dom.Appendable');
goog.provide('goog.dom.DomHelper');
goog.provide('goog.dom.NodeType');

goog.require('goog.array');
goog.require('goog.dom.BrowserFeature');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classes');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Size');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.userAgent');


/**
 * @define {boolean} Whether we know at compile time that the browser is in
 * quirks mode.
 */
goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);


/**
 * @define {boolean} Whether we know at compile time that the browser is in
 * standards compliance mode.
 */
goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);


/**
 * Whether we know the compatibility mode at compile time.
 * @type {boolean}
 * @private
 */
goog.dom.COMPAT_MODE_KNOWN_ =
    goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;


/**
 * Enumeration for DOM node types (for reference)
 * @enum {number}
 */
goog.dom.NodeType = {
  ELEMENT: 1,
  ATTRIBUTE: 2,
  TEXT: 3,
  CDATA_SECTION: 4,
  ENTITY_REFERENCE: 5,
  ENTITY: 6,
  PROCESSING_INSTRUCTION: 7,
  COMMENT: 8,
  DOCUMENT: 9,
  DOCUMENT_TYPE: 10,
  DOCUMENT_FRAGMENT: 11,
  NOTATION: 12
};


/**
 * Gets the DomHelper object for the document where the element resides.
 * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
 *     element.
 * @return {!goog.dom.DomHelper} The DomHelper.
 */
goog.dom.getDomHelper = function(opt_element) {
  return opt_element ?
      new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
      (goog.dom.defaultDomHelper_ ||
          (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
};


/**
 * Cached default DOM helper.
 * @type {goog.dom.DomHelper}
 * @private
 */
goog.dom.defaultDomHelper_;


/**
 * Gets the document object being used by the dom library.
 * @return {!Document} Document object.
 */
goog.dom.getDocument = function() {
  return document;
};


/**
 * Alias for getElementById. If a DOM node is passed in then we just return
 * that.
 * @param {string|Element} element Element ID or a DOM node.
 * @return {Element} The element with the given ID, or the node passed in.
 */
goog.dom.getElement = function(element) {
  return goog.isString(element) ?
      document.getElementById(element) : element;
};


/**
 * Alias for getElement.
 * @param {string|Element} element Element ID or a DOM node.
 * @return {Element} The element with the given ID, or the node passed in.
 * @deprecated Use {@link goog.dom.getElement} instead.
 */
goog.dom.$ = goog.dom.getElement;


/**
 * Looks up elements by both tag and class name, using browser native functions
 * ({@code querySelectorAll}, {@code getElementsByTagName} or
 * {@code getElementsByClassName}) where possible. This function
 * is a useful, if limited, way of collecting a list of DOM elements
 * with certain characteristics.  {@code goog.dom.query} offers a
 * more powerful and general solution which allows matching on CSS3
 * selector expressions, but at increased cost in code size. If all you
 * need is particular tags belonging to a single class, this function
 * is fast and sleek.
 *
 * @see {goog.dom.query}
 *
 * @param {?string=} opt_tag Element tag name.
 * @param {?string=} opt_class Optional class name.
 * @param {(Document|Element)=} opt_el Optional element to look in.
 * @return { {length: number} } Array-like list of elements (only a length
 *     property and numerical indices are guaranteed to exist).
 */
goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
  return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
                                                opt_el);
};


/**
 * Returns an array of all the elements with the provided className.
 * @see {goog.dom.query}
 * @param {string} className the name of the class to look for.
 * @param {(Document|Element)=} opt_el Optional element to look in.
 * @return { {length: number} } The items found with the class name provided.
 */
goog.dom.getElementsByClass = function(className, opt_el) {
  var parent = opt_el || document;
  if (goog.dom.canUseQuerySelector_(parent)) {
    return parent.querySelectorAll('.' + className);
  } else if (parent.getElementsByClassName) {
    return parent.getElementsByClassName(className);
  }
  return goog.dom.getElementsByTagNameAndClass_(
      document, '*', className, opt_el);
};


/**
 * Returns the first element with the provided className.
 * @see {goog.dom.query}
 * @param {string} className the name of the class to look for.
 * @param {Element|Document=} opt_el Optional element to look in.
 * @return {Element} The first item with the class name provided.
 */
goog.dom.getElementByClass = function(className, opt_el) {
  var parent = opt_el || document;
  var retVal = null;
  if (goog.dom.canUseQuerySelector_(parent)) {
    retVal = parent.querySelector('.' + className);
  } else {
    retVal = goog.dom.getElementsByClass(className, opt_el)[0];
  }
  return retVal || null;
};


/**
 * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
 * fast W3C Selectors API.
 * @param {!(Element|Document)} parent The parent document object.
 * @return {boolean} whether or not we can use parent.querySelector* APIs.
 * @private
 */
goog.dom.canUseQuerySelector_ = function(parent) {
  return !!(parent.querySelectorAll && parent.querySelector);
};


/**
 * Helper for {@code getElementsByTagNameAndClass}.
 * @param {!Document} doc The document to get the elements in.
 * @param {?string=} opt_tag Element tag name.
 * @param {?string=} opt_class Optional class name.
 * @param {(Document|Element)=} opt_el Optional element to look in.
 * @return { {length: number} } Array-like list of elements (only a length
 *     property and numerical indices are guaranteed to exist).
 * @private
 */
goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
                                                  opt_el) {
  var parent = opt_el || doc;
  var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';

  if (goog.dom.canUseQuerySelector_(parent) &&
      (tagName || opt_class)) {
    var query = tagName + (opt_class ? '.' + opt_class : '');
    return parent.querySelectorAll(query);
  }

  // Use the native getElementsByClassName if available, under the assumption
  // that even when the tag name is specified, there will be fewer elements to
  // filter through when going by class than by tag name
  if (opt_class && parent.getElementsByClassName) {
    var els = parent.getElementsByClassName(opt_class);

    if (tagName) {
      var arrayLike = {};
      var len = 0;

      // Filter for specific tags if requested.
      for (var i = 0, el; el = els[i]; i++) {
        if (tagName == el.nodeName) {
          arrayLike[len++] = el;
        }
      }
      arrayLike.length = len;

      return arrayLike;
    } else {
      return els;
    }
  }

  var els = parent.getElementsByTagName(tagName || '*');

  if (opt_class) {
    var arrayLike = {};
    var len = 0;
    for (var i = 0, el; el = els[i]; i++) {
      var className = el.className;
      // Check if className has a split function since SVG className does not.
      if (typeof className.split == 'function' &&
          goog.array.contains(className.split(/\s+/), opt_class)) {
        arrayLike[len++] = el;
      }
    }
    arrayLike.length = len;
    return arrayLike;
  } else {
    return els;
  }
};


/**
 * Alias for {@code getElementsByTagNameAndClass}.
 * @param {?string=} opt_tag Element tag name.
 * @param {?string=} opt_class Optional class name.
 * @param {Element=} opt_el Optional element to look in.
 * @return { {length: number} } Array-like list of elements (only a length
 *     property and numerical indices are guaranteed to exist).
 * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
 */
goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;


/**
 * Sets multiple properties on a node.
 * @param {Element} element DOM node to set properties on.
 * @param {Object} properties Hash of property:value pairs.
 */
goog.dom.setProperties = function(element, properties) {
  goog.object.forEach(properties, function(val, key) {
    if (key == 'style') {
      element.style.cssText = val;
    } else if (key == 'class') {
      element.className = val;
    } else if (key == 'for') {
      element.htmlFor = val;
    } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
      element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
    } else if (goog.string.startsWith(key, 'aria-') ||
        goog.string.startsWith(key, 'data-')) {
      element.setAttribute(key, val);
    } else {
      element[key] = val;
    }
  });
};


/**
 * Map of attributes that should be set using
 * element.setAttribute(key, val) instead of element[key] = val.  Used
 * by goog.dom.setProperties.
 *
 * @type {Object}
 * @private
 */
goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
  'cellpadding': 'cellPadding',
  'cellspacing': 'cellSpacing',
  'colspan': 'colSpan',
  'frameborder': 'frameBorder',
  'height': 'height',
  'maxlength': 'maxLength',
  'role': 'role',
  'rowspan': 'rowSpan',
  'type': 'type',
  'usemap': 'useMap',
  'valign': 'vAlign',
  'width': 'width'
};


/**
 * Gets the dimensions of the viewport.
 *
 * Gecko Standards mode:
 * docEl.clientWidth  Width of viewport excluding scrollbar.
 * win.innerWidth     Width of viewport including scrollbar.
 * body.clientWidth   Width of body element.
 *
 * docEl.clientHeight Height of viewport excluding scrollbar.
 * win.innerHeight    Height of viewport including scrollbar.
 * body.clientHeight  Height of document.
 *
 * Gecko Backwards compatible mode:
 * docEl.clientWidth  Width of viewport excluding scrollbar.
 * win.innerWidth     Width of viewport including scrollbar.
 * body.clientWidth   Width of viewport excluding scrollbar.
 *
 * docEl.clientHeight Height of document.
 * win.innerHeight    Height of viewport including scrollbar.
 * body.clientHeight  Height of viewport excluding scrollbar.
 *
 * IE6/7 Standards mode:
 * docEl.clientWidth  Width of viewport excluding scrollbar.
 * win.innerWidth     Undefined.
 * body.clientWidth   Width of body element.
 *
 * docEl.clientHeight Height of viewport excluding scrollbar.
 * win.innerHeight    Undefined.
 * body.clientHeight  Height of document element.
 *
 * IE5 + IE6/7 Backwards compatible mode:
 * docEl.clientWidth  0.
 * win.innerWidth     Undefined.
 * body.clientWidth   Width of viewport excluding scrollbar.
 *
 * docEl.clientHeight 0.
 * win.innerHeight    Undefined.
 * body.clientHeight  Height of viewport excluding scrollbar.
 *
 * Opera 9 Standards and backwards compatible mode:
 * docEl.clientWidth  Width of viewport excluding scrollbar.
 * win.innerWidth     Width of viewport including scrollbar.
 * body.clientWidth   Width of viewport excluding scrollbar.
 *
 * docEl.clientHeight Height of document.
 * win.innerHeight    Height of viewport including scrollbar.
 * body.clientHeight  Height of viewport excluding scrollbar.
 *
 * WebKit:
 * Safari 2
 * docEl.clientHeight Same as scrollHeight.
 * docEl.clientWidth  Same as innerWidth.
 * win.innerWidth     Width of viewport excluding scrollbar.
 * win.innerHeight    Height of the viewport including scrollbar.
 * frame.innerHeight  Height of the viewport exluding scrollbar.
 *
 * Safari 3 (tested in 522)
 *
 * docEl.clientWidth  Width of viewport excluding scrollbar.
 * docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
 * body.clientHeight  Height of viewport excluding scrollbar in quirks mode.
 *
 * @param {Window=} opt_window Optional window element to test.
 * @return {!goog.math.Size} Object with values 'width' and 'height'.
 */
goog.dom.getViewportSize = function(opt_window) {
  // TODO(arv): This should not take an argument
  return goog.dom.getViewportSize_(opt_window || window);
};


/**
 * Helper for {@code getViewportSize}.
 * @param {Window} win The window to get the view port size for.
 * @return {!goog.math.Size} Object with values 'width' and 'height'.
 * @private
 */
goog.dom.getViewportSize_ = function(win) {
  var doc = win.document;
  var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
  return new goog.math.Size(el.clientWidth, el.clientHeight);
};


/**
 * Calculates the height of the document.
 *
 * @return {number} The height of the current document.
 */
goog.dom.getDocumentHeight = function() {
  return goog.dom.getDocumentHeight_(window);
};


/**
 * Calculates the height of the document of the given window.
 *
 * Function code copied from the opensocial gadget api:
 *   gadgets.window.adjustHeight(opt_height)
 *
 * @private
 * @param {Window} win The window whose document height to retrieve.
 * @return {number} The height of the document of the given window.
 */
goog.dom.getDocumentHeight_ = function(win) {
  // NOTE(eae): This method will return the window size rather than the document
  // size in webkit quirks mode.
  var doc = win.document;
  var height = 0;

  if (doc) {
    // Calculating inner content height is hard and different between
    // browsers rendering in Strict vs. Quirks mode.  We use a combination of
    // three properties within document.body and document.documentElement:
    // - scrollHeight
    // - offsetHeight
    // - clientHeight
    // These values differ significantly between browsers and rendering modes.
    // But there are patterns.  It just takes a lot of time and persistence
    // to figure out.

    // Get the height of the viewport
    var vh = goog.dom.getViewportSize_(win).height;
    var body = doc.body;
    var docEl = doc.documentElement;
    if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
      // In Strict mode:
      // The inner content height is contained in either:
      //    document.documentElement.scrollHeight
      //    document.documentElement.offsetHeight
      // Based on studying the values output by different browsers,
      // use the value that's NOT equal to the viewport height found above.
      height = docEl.scrollHeight != vh ?
          docEl.scrollHeight : docEl.offsetHeight;
    } else {
      // In Quirks mode:
      // documentElement.clientHeight is equal to documentElement.offsetHeight
      // except in IE.  In most browsers, document.documentElement can be used
      // to calculate the inner content height.
      // However, in other browsers (e.g. IE), document.body must be used
      // instead.  How do we know which one to use?
      // If document.documentElement.clientHeight does NOT equal
      // document.documentElement.offsetHeight, then use document.body.
      var sh = docEl.scrollHeight;
      var oh = docEl.offsetHeight;
      if (docEl.clientHeight != oh) {
        sh = body.scrollHeight;
        oh = body.offsetHeight;
      }

      // Detect whether the inner content height is bigger or smaller
      // than the bounding box (viewport).  If bigger, take the larger
      // value.  If smaller, take the smaller value.
      if (sh > vh) {
        // Content is larger
        height = sh > oh ? sh : oh;
      } else {
        // Content is smaller
        height = sh < oh ? sh : oh;
      }
    }
  }

  return height;
};


/**
 * Gets the page scroll distance as a coordinate object.
 *
 * @param {Window=} opt_window Optional window element to test.
 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
 * @deprecated Use {@link goog.dom.getDocumentScroll} instead.
 */
goog.dom.getPageScroll = function(opt_window) {
  var win = opt_window || goog.global || window;
  return goog.dom.getDomHelper(win.document).getDocumentScroll();
};


/**
 * Gets the document scroll distance as a coordinate object.
 *
 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
 */
goog.dom.getDocumentScroll = function() {
  return goog.dom.getDocumentScroll_(document);
};


/**
 * Helper for {@code getDocumentScroll}.
 *
 * @param {!Document} doc The document to get the scroll for.
 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
 * @private
 */
goog.dom.getDocumentScroll_ = function(doc) {
  var el = goog.dom.getDocumentScrollElement_(doc);
  var win = goog.dom.getWindow_(doc);
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
      win.pageYOffset != el.scrollTop) {
    // The keyboard on IE10 touch devices shifts the page using the pageYOffset
    // without modifying scrollTop. For this case, we want the body scroll
    // offsets.
    return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
  }
  return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft,
      win.pageYOffset || el.scrollTop);
};


/**
 * Gets the document scroll element.
 * @return {Element} Scrolling element.
 */
goog.dom.getDocumentScrollElement = function() {
  return goog.dom.getDocumentScrollElement_(document);
};


/**
 * Helper for {@code getDocumentScrollElement}.
 * @param {!Document} doc The document to get the scroll element for.
 * @return {Element} Scrolling element.
 * @private
 */
goog.dom.getDocumentScrollElement_ = function(doc) {
  // Safari (2 and 3) needs body.scrollLeft in both quirks mode and strict mode.
  return !goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc) ?
      doc.documentElement : doc.body;
};


/**
 * Gets the window object associated with the given document.
 *
 * @param {Document=} opt_doc  Document object to get window for.
 * @return {!Window} The window associated with the given document.
 */
goog.dom.getWindow = function(opt_doc) {
  // TODO(arv): This should not take an argument.
  return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
};


/**
 * Helper for {@code getWindow}.
 *
 * @param {!Document} doc  Document object to get window for.
 * @return {!Window} The window associated with the given document.
 * @private
 */
goog.dom.getWindow_ = function(doc) {
  return doc.parentWindow || doc.defaultView;
};


/**
 * Returns a dom node with a set of attributes.  This function accepts varargs
 * for subsequent nodes to be added.  Subsequent nodes will be added to the
 * first node as childNodes.
 *
 * So:
 * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
 * would return a div with two child paragraphs
 *
 * @param {string} tagName Tag to create.
 * @param {(Object|Array.<string>|string)=} opt_attributes If object, then a map
 *     of name-value pairs for attributes. If a string, then this is the
 *     className of the new element. If an array, the elements will be joined
 *     together as the className of the new element.
 * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
 *     strings for text nodes. If one of the var_args is an array or NodeList,i
 *     its elements will be added as childNodes instead.
 * @return {!Element} Reference to a DOM node.
 */
goog.dom.createDom = function(tagName, opt_attributes, var_args) {
  return goog.dom.createDom_(document, arguments);
};


/**
 * Helper for {@code createDom}.
 * @param {!Document} doc The document to create the DOM in.
 * @param {!Arguments} args Argument object passed from the callers. See
 *     {@code goog.dom.createDom} for details.
 * @return {!Element} Reference to a DOM node.
 * @private
 */
goog.dom.createDom_ = function(doc, args) {
  var tagName = args[0];
  var attributes = args[1];

  // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
  //                            dhtml/reference/properties/name_2.asp
  // Also does not allow setting of 'type' attribute on 'input' or 'button'.
  if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
      (attributes.name || attributes.type)) {
    var tagNameArr = ['<', tagName];
    if (attributes.name) {
      tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
                      '"');
    }
    if (attributes.type) {
      tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
                      '"');

      // Clone attributes map to remove 'type' without mutating the input.
      var clone = {};
      goog.object.extend(clone, attributes);

      // JSCompiler can't see how goog.object.extend added this property,
      // because it was essentially added by reflection.
      // So it needs to be quoted.
      delete clone['type'];

      attributes = clone;
    }
    tagNameArr.push('>');
    tagName = tagNameArr.join('');
  }

  var element = doc.createElement(tagName);

  if (attributes) {
    if (goog.isString(attributes)) {
      element.className = attributes;
    } else if (goog.isArray(attributes)) {
      goog.dom.classes.add.apply(null, [element].concat(attributes));
    } else {
      goog.dom.setProperties(element, attributes);
    }
  }

  if (args.length > 2) {
    goog.dom.append_(doc, element, args, 2);
  }

  return element;
};


/**
 * Appends a node with text or other nodes.
 * @param {!Document} doc The document to create new nodes in.
 * @param {!Node} parent The node to append nodes to.
 * @param {!Arguments} args The values to add. See {@code goog.dom.append}.
 * @param {number} startIndex The index of the array to start from.
 * @private
 */
goog.dom.append_ = function(doc, parent, args, startIndex) {
  function childHandler(child) {
    // TODO(user): More coercion, ala MochiKit?
    if (child) {
      parent.appendChild(goog.isString(child) ?
          doc.createTextNode(child) : child);
    }
  }

  for (var i = startIndex; i < args.length; i++) {
    var arg = args[i];
    // TODO(attila): Fix isArrayLike to return false for a text node.
    if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
      // If the argument is a node list, not a real array, use a clone,
      // because forEach can't be used to mutate a NodeList.
      goog.array.forEach(goog.dom.isNodeList(arg) ?
          goog.array.toArray(arg) : arg,
          childHandler);
    } else {
      childHandler(arg);
    }
  }
};


/**
 * Alias for {@code createDom}.
 * @param {string} tagName Tag to create.
 * @param {(string|Object)=} opt_attributes If object, then a map of name-value
 *     pairs for attributes. If a string, then this is the className of the new
 *     element.
 * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
 *     strings for text nodes. If one of the var_args is an array, its
 *     children will be added as childNodes instead.
 * @return {!Element} Reference to a DOM node.
 * @deprecated Use {@link goog.dom.createDom} instead.
 */
goog.dom.$dom = goog.dom.createDom;


/**
 * Creates a new element.
 * @param {string} name Tag name.
 * @return {!Element} The new element.
 */
goog.dom.createElement = function(name) {
  return document.createElement(name);
};


/**
 * Creates a new text node.
 * @param {number|string} content Content.
 * @return {!Text} The new text node.
 */
goog.dom.createTextNode = function(content) {
  return document.createTextNode(String(content));
};


/**
 * Create a table.
 * @param {number} rows The number of rows in the table.  Must be >= 1.
 * @param {number} columns The number of columns in the table.  Must be >= 1.
 * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
 * @return {!Element} The created table.
 */
goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
  return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
};


/**
 * Create a table.
 * @param {!Document} doc Document object to use to create the table.
 * @param {number} rows The number of rows in the table.  Must be >= 1.
 * @param {number} columns The number of columns in the table.  Must be >= 1.
 * @param {boolean} fillWithNbsp If true, fills table entries with nsbps.
 * @return {!Element} The created table.
 * @private
 */
goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
  var rowHtml = ['<tr>'];
  for (var i = 0; i < columns; i++) {
    rowHtml.push(fillWithNbsp ? '<td>&nbsp;</td>' : '<td></td>');
  }
  rowHtml.push('</tr>');
  rowHtml = rowHtml.join('');
  var totalHtml = ['<table>'];
  for (i = 0; i < rows; i++) {
    totalHtml.push(rowHtml);
  }
  totalHtml.push('</table>');

  var elem = doc.createElement(goog.dom.TagName.DIV);
  elem.innerHTML = totalHtml.join('');
  return /** @type {!Element} */ (elem.removeChild(elem.firstChild));
};


/**
 * Converts an HTML string into a document fragment. The string must be
 * sanitized in order to avoid cross-site scripting. For example
 * {@code goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;')}
 * triggers an alert in all browsers, even if the returned document fragment
 * is thrown away immediately.
 *
 * @param {string} htmlString The HTML string to convert.
 * @return {!Node} The resulting document fragment.
 */
goog.dom.htmlToDocumentFragment = function(htmlString) {
  return goog.dom.htmlToDocumentFragment_(document, htmlString);
};


/**
 * Helper for {@code htmlToDocumentFragment}.
 *
 * @param {!Document} doc The document.
 * @param {string} htmlString The HTML string to convert.
 * @return {!Node} The resulting document fragment.
 * @private
 */
goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
  var tempDiv = doc.createElement('div');
  if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
    tempDiv.innerHTML = '<br>' + htmlString;
    tempDiv.removeChild(tempDiv.firstChild);
  } else {
    tempDiv.innerHTML = htmlString;
  }
  if (tempDiv.childNodes.length == 1) {
    return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild));
  } else {
    var fragment = doc.createDocumentFragment();
    while (tempDiv.firstChild) {
      fragment.appendChild(tempDiv.firstChild);
    }
    return fragment;
  }
};


/**
 * Returns the compatMode of the document.
 * @return {string} The result is either CSS1Compat or BackCompat.
 * @deprecated use goog.dom.isCss1CompatMode instead.
 */
goog.dom.getCompatMode = function() {
  return goog.dom.isCss1CompatMode() ? 'CSS1Compat' : 'BackCompat';
};


/**
 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
 * mode, false otherwise.
 * @return {boolean} True if in CSS1-compatible mode.
 */
goog.dom.isCss1CompatMode = function() {
  return goog.dom.isCss1CompatMode_(document);
};


/**
 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
 * mode, false otherwise.
 * @param {Document} doc The document to check.
 * @return {boolean} True if in CSS1-compatible mode.
 * @private
 */
goog.dom.isCss1CompatMode_ = function(doc) {
  if (goog.dom.COMPAT_MODE_KNOWN_) {
    return goog.dom.ASSUME_STANDARDS_MODE;
  }

  return doc.compatMode == 'CSS1Compat';
};


/**
 * Determines if the given node can contain children, intended to be used for
 * HTML generation.
 *
 * IE natively supports node.canHaveChildren but has inconsistent behavior.
 * Prior to IE8 the base tag allows children and in IE9 all nodes return true
 * for canHaveChildren.
 *
 * In practice all non-IE browsers allow you to add children to any node, but
 * the behavior is inconsistent:
 *
 * <pre>
 *   var a = document.createElement('br');
 *   a.appendChild(document.createTextNode('foo'));
 *   a.appendChild(document.createTextNode('bar'));
 *   console.log(a.childNodes.length);  // 2
 *   console.log(a.innerHTML);  // Chrome: "", IE9: "foobar", FF3.5: "foobar"
 * </pre>
 *
 * For more information, see:
 * http://dev.w3.org/html5/markup/syntax.html#syntax-elements
 *
 * TODO(user): Rename shouldAllowChildren() ?
 *
 * @param {Node} node The node to check.
 * @return {boolean} Whether the node can contain children.
 */
goog.dom.canHaveChildren = function(node) {
  if (node.nodeType != goog.dom.NodeType.ELEMENT) {
    return false;
  }
  switch (node.tagName) {
    case goog.dom.TagName.APPLET:
    case goog.dom.TagName.AREA:
    case goog.dom.TagName.BASE:
    case goog.dom.TagName.BR:
    case goog.dom.TagName.COL:
    case goog.dom.TagName.COMMAND:
    case goog.dom.TagName.EMBED:
    case goog.dom.TagName.FRAME:
    case goog.dom.TagName.HR:
    case goog.dom.TagName.IMG:
    case goog.dom.TagName.INPUT:
    case goog.dom.TagName.IFRAME:
    case goog.dom.TagName.ISINDEX:
    case goog.dom.TagName.KEYGEN:
    case goog.dom.TagName.LINK:
    case goog.dom.TagName.NOFRAMES:
    case goog.dom.TagName.NOSCRIPT:
    case goog.dom.TagName.META:
    case goog.dom.TagName.OBJECT:
    case goog.dom.TagName.PARAM:
    case goog.dom.TagName.SCRIPT:
    case goog.dom.TagName.SOURCE:
    case goog.dom.TagName.STYLE:
    case goog.dom.TagName.TRACK:
    case goog.dom.TagName.WBR:
      return false;
  }
  return true;
};


/**
 * Appends a child to a node.
 * @param {Node} parent Parent.
 * @param {Node} child Child.
 */
goog.dom.appendChild = function(parent, child) {
  parent.appendChild(child);
};


/**
 * Appends a node with text or other nodes.
 * @param {!Node} parent The node to append nodes to.
 * @param {...goog.dom.Appendable} var_args The things to append to the node.
 *     If this is a Node it is appended as is.
 *     If this is a string then a text node is appended.
 *     If this is an array like object then fields 0 to length - 1 are appended.
 */
goog.dom.append = function(parent, var_args) {
  goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
};


/**
 * Removes all the child nodes on a DOM node.
 * @param {Node} node Node to remove children from.
 */
goog.dom.removeChildren = function(node) {
  // Note: Iterations over live collections can be slow, this is the fastest
  // we could find. The double parenthesis are used to prevent JsCompiler and
  // strict warnings.
  var child;
  while ((child = node.firstChild)) {
    node.removeChild(child);
  }
};


/**
 * Inserts a new node before an existing reference node (i.e. as the previous
 * sibling). If the reference node has no parent, then does nothing.
 * @param {Node} newNode Node to insert.
 * @param {Node} refNode Reference node to insert before.
 */
goog.dom.insertSiblingBefore = function(newNode, refNode) {
  if (refNode.parentNode) {
    refNode.parentNode.insertBefore(newNode, refNode);
  }
};


/**
 * Inserts a new node after an existing reference node (i.e. as the next
 * sibling). If the reference node has no parent, then does nothing.
 * @param {Node} newNode Node to insert.
 * @param {Node} refNode Reference node to insert after.
 */
goog.dom.insertSiblingAfter = function(newNode, refNode) {
  if (refNode.parentNode) {
    refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
  }
};


/**
 * Insert a child at a given index. If index is larger than the number of child
 * nodes that the parent currently has, the node is inserted as the last child
 * node.
 * @param {Element} parent The element into which to insert the child.
 * @param {Node} child The element to insert.
 * @param {number} index The index at which to insert the new child node. Must
 *     not be negative.
 */
goog.dom.insertChildAt = function(parent, child, index) {
  // Note that if the second argument is null, insertBefore
  // will append the child at the end of the list of children.
  parent.insertBefore(child, parent.childNodes[index] || null);
};


/**
 * Removes a node from its parent.
 * @param {Node} node The node to remove.
 * @return {Node} The node removed if removed; else, null.
 */
goog.dom.removeNode = function(node) {
  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
};


/**
 * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
 * parent.
 * @param {Node} newNode Node to insert.
 * @param {Node} oldNode Node to replace.
 */
goog.dom.replaceNode = function(newNode, oldNode) {
  var parent = oldNode.parentNode;
  if (parent) {
    parent.replaceChild(newNode, oldNode);
  }
};


/**
 * Flattens an element. That is, removes it and replace it with its children.
 * Does nothing if the element is not in the document.
 * @param {Element} element The element to flatten.
 * @return {Element|undefined} The original element, detached from the document
 *     tree, sans children; or undefined, if the element was not in the document
 *     to begin with.
 */
goog.dom.flattenElement = function(element) {
  var child, parent = element.parentNode;
  if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
    // Use IE DOM method (supported by Opera too) if available
    if (element.removeNode) {
      return /** @type {Element} */ (element.removeNode(false));
    } else {
      // Move all children of the original node up one level.
      while ((child = element.firstChild)) {
        parent.insertBefore(child, element);
      }

      // Detach the original element.
      return /** @type {Element} */ (goog.dom.removeNode(element));
    }
  }
};


/**
 * Returns an array containing just the element children of the given element.
 * @param {Element} element The element whose element children we want.
 * @return {!(Array|NodeList)} An array or array-like list of just the element
 *     children of the given element.
 */
goog.dom.getChildren = function(element) {
  // We check if the children attribute is supported for child elements
  // since IE8 misuses the attribute by also including comments.
  if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
      element.children != undefined) {
    return element.children;
  }
  // Fall back to manually filtering the element's child nodes.
  return goog.array.filter(element.childNodes, function(node) {
    return node.nodeType == goog.dom.NodeType.ELEMENT;
  });
};


/**
 * Returns the first child node that is an element.
 * @param {Node} node The node to get the first child element of.
 * @return {Element} The first child node of {@code node} that is an element.
 */
goog.dom.getFirstElementChild = function(node) {
  if (node.firstElementChild != undefined) {
    return /** @type {Element} */(node).firstElementChild;
  }
  return goog.dom.getNextElementNode_(node.firstChild, true);
};


/**
 * Returns the last child node that is an element.
 * @param {Node} node The node to get the last child element of.
 * @return {Element} The last child node of {@code node} that is an element.
 */
goog.dom.getLastElementChild = function(node) {
  if (node.lastElementChild != undefined) {
    return /** @type {Element} */(node).lastElementChild;
  }
  return goog.dom.getNextElementNode_(node.lastChild, false);
};


/**
 * Returns the first next sibling that is an element.
 * @param {Node} node The node to get the next sibling element of.
 * @return {Element} The next sibling of {@code node} that is an element.
 */
goog.dom.getNextElementSibling = function(node) {
  if (node.nextElementSibling != undefined) {
    return /** @type {Element} */(node).nextElementSibling;
  }
  return goog.dom.getNextElementNode_(node.nextSibling, true);
};


/**
 * Returns the first previous sibling that is an element.
 * @param {Node} node The node to get the previous sibling element of.
 * @return {Element} The first previous sibling of {@code node} that is
 *     an element.
 */
goog.dom.getPreviousElementSibling = function(node) {
  if (node.previousElementSibling != undefined) {
    return /** @type {Element} */(node).previousElementSibling;
  }
  return goog.dom.getNextElementNode_(node.previousSibling, false);
};


/**
 * Returns the first node that is an element in the specified direction,
 * starting with {@code node}.
 * @param {Node} node The node to get the next element from.
 * @param {boolean} forward Whether to look forwards or backwards.
 * @return {Element} The first element.
 * @private
 */
goog.dom.getNextElementNode_ = function(node, forward) {
  while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
    node = forward ? node.nextSibling : node.previousSibling;
  }

  return /** @type {Element} */ (node);
};


/**
 * Returns the next node in source order from the given node.
 * @param {Node} node The node.
 * @return {Node} The next node in the DOM tree, or null if this was the last
 *     node.
 */
goog.dom.getNextNode = function(node) {
  if (!node) {
    return null;
  }

  if (node.firstChild) {
    return node.firstChild;
  }

  while (node && !node.nextSibling) {
    node = node.parentNode;
  }

  return node ? node.nextSibling : null;
};


/**
 * Returns the previous node in source order from the given node.
 * @param {Node} node The node.
 * @return {Node} The previous node in the DOM tree, or null if this was the
 *     first node.
 */
goog.dom.getPreviousNode = function(node) {
  if (!node) {
    return null;
  }

  if (!node.previousSibling) {
    return node.parentNode;
  }

  node = node.previousSibling;
  while (node && node.lastChild) {
    node = node.lastChild;
  }

  return node;
};


/**
 * Whether the object looks like a DOM node.
 * @param {*} obj The object being tested for node likeness.
 * @return {boolean} Whether the object looks like a DOM node.
 */
goog.dom.isNodeLike = function(obj) {
  return goog.isObject(obj) && obj.nodeType > 0;
};


/**
 * Whether the object looks like an Element.
 * @param {*} obj The object being tested for Element likeness.
 * @return {boolean} Whether the object looks like an Element.
 */
goog.dom.isElement = function(obj) {
  return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
};


/**
 * Returns true if the specified value is a Window object. This includes the
 * global window for HTML pages, and iframe windows.
 * @param {*} obj Variable to test.
 * @return {boolean} Whether the variable is a window.
 */
goog.dom.isWindow = function(obj) {
  return goog.isObject(obj) && obj['window'] == obj;
};


/**
 * Returns an element's parent, if it's an Element.
 * @param {Element} element The DOM element.
 * @return {Element} The parent, or null if not an Element.
 */
goog.dom.getParentElement = function(element) {
  if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
    var isIe9 = goog.userAgent.IE &&
        goog.userAgent.isVersionOrHigher('9') &&
        !goog.userAgent.isVersionOrHigher('10');
    // SVG elements in IE9 can't use the parentElement property.
    // goog.global['SVGElement'] is not defined in IE9 quirks mode.
    if (!(isIe9 && goog.global['SVGElement'] &&
        element instanceof goog.global['SVGElement'])) {
      return element.parentElement;
    }
  }
  var parent = element.parentNode;
  return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
};


/**
 * Whether a node contains another node.
 * @param {Node} parent The node that should contain the other node.
 * @param {Node} descendant The node to test presence of.
 * @return {boolean} Whether the parent node contains the descendent node.
 */
goog.dom.contains = function(parent, descendant) {
  // We use browser specific methods for this if available since it is faster
  // that way.

  // IE DOM
  if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
    return parent == descendant || parent.contains(descendant);
  }

  // W3C DOM Level 3
  if (typeof parent.compareDocumentPosition != 'undefined') {
    return parent == descendant ||
        Boolean(parent.compareDocumentPosition(descendant) & 16);
  }

  // W3C DOM Level 1
  while (descendant && parent != descendant) {
    descendant = descendant.parentNode;
  }
  return descendant == parent;
};


/**
 * Compares the document order of two nodes, returning 0 if they are the same
 * node, a negative number if node1 is before node2, and a positive number if
 * node2 is before node1.  Note that we compare the order the tags appear in the
 * document so in the tree <b><i>text</i></b> the B node is considered to be
 * before the I node.
 *
 * @param {Node} node1 The first node to compare.
 * @param {Node} node2 The second node to compare.
 * @return {number} 0 if the nodes are the same node, a negative number if node1
 *     is before node2, and a positive number if node2 is before node1.
 */
goog.dom.compareNodeOrder = function(node1, node2) {
  // Fall out quickly for equality.
  if (node1 == node2) {
    return 0;
  }

  // Use compareDocumentPosition where available
  if (node1.compareDocumentPosition) {
    // 4 is the bitmask for FOLLOWS.
    return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
  }

  // Special case for document nodes on IE 7 and 8.
  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
    if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
      return -1;
    }
    if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
      return 1;
    }
  }

  // Process in IE using sourceIndex - we check to see if the first node has
  // a source index or if its parent has one.
  if ('sourceIndex' in node1 ||
      (node1.parentNode && 'sourceIndex' in node1.parentNode)) {
    var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
    var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;

    if (isElement1 && isElement2) {
      return node1.sourceIndex - node2.sourceIndex;
    } else {
      var parent1 = node1.parentNode;
      var parent2 = node2.parentNode;

      if (parent1 == parent2) {
        return goog.dom.compareSiblingOrder_(node1, node2);
      }

      if (!isElement1 && goog.dom.contains(parent1, node2)) {
        return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
      }


      if (!isElement2 && goog.dom.contains(parent2, node1)) {
        return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
      }

      return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
             (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
    }
  }

  // For Safari, we compare ranges.
  var doc = goog.dom.getOwnerDocument(node1);

  var range1, range2;
  range1 = doc.createRange();
  range1.selectNode(node1);
  range1.collapse(true);

  range2 = doc.createRange();
  range2.selectNode(node2);
  range2.collapse(true);

  return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
      range2);
};


/**
 * Utility function to compare the position of two nodes, when
 * {@code textNode}'s parent is an ancestor of {@code node}.  If this entry
 * condition is not met, this function will attempt to reference a null object.
 * @param {Node} textNode The textNode to compare.
 * @param {Node} node The node to compare.
 * @return {number} -1 if node is before textNode, +1 otherwise.
 * @private
 */
goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
  var parent = textNode.parentNode;
  if (parent == node) {
    // If textNode is a child of node, then node comes first.
    return -1;
  }
  var sibling = node;
  while (sibling.parentNode != parent) {
    sibling = sibling.parentNode;
  }
  return goog.dom.compareSiblingOrder_(sibling, textNode);
};


/**
 * Utility function to compare the position of two nodes known to be non-equal
 * siblings.
 * @param {Node} node1 The first node to compare.
 * @param {Node} node2 The second node to compare.
 * @return {number} -1 if node1 is before node2, +1 otherwise.
 * @private
 */
goog.dom.compareSiblingOrder_ = function(node1, node2) {
  var s = node2;
  while ((s = s.previousSibling)) {
    if (s == node1) {
      // We just found node1 before node2.
      return -1;
    }
  }

  // Since we didn't find it, node1 must be after node2.
  return 1;
};


/**
 * Find the deepest common ancestor of the given nodes.
 * @param {...Node} var_args The nodes to find a common ancestor of.
 * @return {Node} The common ancestor of the nodes, or null if there is none.
 *     null will only be returned if two or more of the nodes are from different
 *     documents.
 */
goog.dom.findCommonAncestor = function(var_args) {
  var i, count = arguments.length;
  if (!count) {
    return null;
  } else if (count == 1) {
    return arguments[0];
  }

  var paths = [];
  var minLength = Infinity;
  for (i = 0; i < count; i++) {
    // Compute the list of ancestors.
    var ancestors = [];
    var node = arguments[i];
    while (node) {
      ancestors.unshift(node);
      node = node.parentNode;
    }

    // Save the list for comparison.
    paths.push(ancestors);
    minLength = Math.min(minLength, ancestors.length);
  }
  var output = null;
  for (i = 0; i < minLength; i++) {
    var first = paths[0][i];
    for (var j = 1; j < count; j++) {
      if (first != paths[j][i]) {
        return output;
      }
    }
    output = first;
  }
  return output;
};


/**
 * Returns the owner document for a node.
 * @param {Node|Window} node The node to get the document for.
 * @return {!Document} The document owning the node.
 */
goog.dom.getOwnerDocument = function(node) {
  // TODO(arv): Remove IE5 code.
  // IE5 uses document instead of ownerDocument
  return /** @type {!Document} */ (
      node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
      node.ownerDocument || node.document);
};


/**
 * Cross-browser function for getting the document element of a frame or iframe.
 * @param {Element} frame Frame element.
 * @return {!Document} The frame content document.
 */
goog.dom.getFrameContentDocument = function(frame) {
  var doc = frame.contentDocument || frame.contentWindow.document;
  return doc;
};


/**
 * Cross-browser function for getting the window of a frame or iframe.
 * @param {Element} frame Frame element.
 * @return {Window} The window associated with the given frame.
 */
goog.dom.getFrameContentWindow = function(frame) {
  return frame.contentWindow ||
      goog.dom.getWindow_(goog.dom.getFrameContentDocument(frame));
};


/**
 * Cross-browser function for setting the text content of an element.
 * @param {Element} element The element to change the text content of.
 * @param {string|number} text The string that should replace the current
 *     element content.
 */
goog.dom.setTextContent = function(element, text) {
  if ('textContent' in element) {
    element.textContent = text;
  } else if (element.firstChild &&
             element.firstChild.nodeType == goog.dom.NodeType.TEXT) {
    // If the first child is a text node we just change its data and remove the
    // rest of the children.
    while (element.lastChild != element.firstChild) {
      element.removeChild(element.lastChild);
    }
    element.firstChild.data = text;
  } else {
    goog.dom.removeChildren(element);
    var doc = goog.dom.getOwnerDocument(element);
    element.appendChild(doc.createTextNode(String(text)));
  }
};


/**
 * Gets the outerHTML of a node, which islike innerHTML, except that it
 * actually contains the HTML of the node itself.
 * @param {Element} element The element to get the HTML of.
 * @return {string} The outerHTML of the given element.
 */
goog.dom.getOuterHtml = function(element) {
  // IE, Opera and WebKit all have outerHTML.
  if ('outerHTML' in element) {
    return element.outerHTML;
  } else {
    var doc = goog.dom.getOwnerDocument(element);
    var div = doc.createElement('div');
    div.appendChild(element.cloneNode(true));
    return div.innerHTML;
  }
};


/**
 * Finds the first descendant node that matches the filter function, using
 * a depth first search. This function offers the most general purpose way
 * of finding a matching element. You may also wish to consider
 * {@code goog.dom.query} which can express many matching criteria using
 * CSS selector expressions. These expressions often result in a more
 * compact representation of the desired result.
 * @see goog.dom.query
 *
 * @param {Node} root The root of the tree to search.
 * @param {function(Node) : boolean} p The filter function.
 * @return {Node|undefined} The found node or undefined if none is found.
 */
goog.dom.findNode = function(root, p) {
  var rv = [];
  var found = goog.dom.findNodes_(root, p, rv, true);
  return found ? rv[0] : undefined;
};


/**
 * Finds all the descendant nodes that match the filter function, using a
 * a depth first search. This function offers the most general-purpose way
 * of finding a set of matching elements. You may also wish to consider
 * {@code goog.dom.query} which can express many matching criteria using
 * CSS selector expressions. These expressions often result in a more
 * compact representation of the desired result.

 * @param {Node} root The root of the tree to search.
 * @param {function(Node) : boolean} p The filter function.
 * @return {!Array.<!Node>} The found nodes or an empty array if none are found.
 */
goog.dom.findNodes = function(root, p) {
  var rv = [];
  goog.dom.findNodes_(root, p, rv, false);
  return rv;
};


/**
 * Finds the first or all the descendant nodes that match the filter function,
 * using a depth first search.
 * @param {Node} root The root of the tree to search.
 * @param {function(Node) : boolean} p The filter function.
 * @param {!Array.<!Node>} rv The found nodes are added to this array.
 * @param {boolean} findOne If true we exit after the first found node.
 * @return {boolean} Whether the search is complete or not. True in case findOne
 *     is true and the node is found. False otherwise.
 * @private
 */
goog.dom.findNodes_ = function(root, p, rv, findOne) {
  if (root != null) {
    var child = root.firstChild;
    while (child) {
      if (p(child)) {
        rv.push(child);
        if (findOne) {
          return true;
        }
      }
      if (goog.dom.findNodes_(child, p, rv, findOne)) {
        return true;
      }
      child = child.nextSibling;
    }
  }
  return false;
};


/**
 * Map of tags whose content to ignore when calculating text length.
 * @type {Object}
 * @private
 */
goog.dom.TAGS_TO_IGNORE_ = {
  'SCRIPT': 1,
  'STYLE': 1,
  'HEAD': 1,
  'IFRAME': 1,
  'OBJECT': 1
};


/**
 * Map of tags which have predefined values with regard to whitespace.
 * @type {Object}
 * @private
 */
goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};


/**
 * Returns true if the element has a tab index that allows it to receive
 * keyboard focus (tabIndex >= 0), false otherwise.  Note that form elements
 * natively support keyboard focus, even if they have no tab index.
 * @param {Element} element Element to check.
 * @return {boolean} Whether the element has a tab index that allows keyboard
 *     focus.
 * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
 */
goog.dom.isFocusableTabIndex = function(element) {
  // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
  // which returns an object with a 'specified' property if tabIndex is
  // specified.  This works on other browsers, too.
  var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
  if (attrNode && attrNode.specified) {
    var index = element.tabIndex;
    // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
    return goog.isNumber(index) && index >= 0 && index < 32768;
  }
  return false;
};


/**
 * Enables or disables keyboard focus support on the element via its tab index.
 * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
 * (or elements that natively support keyboard focus, like form elements) can
 * receive keyboard focus.  See http://go/tabindex for more info.
 * @param {Element} element Element whose tab index is to be changed.
 * @param {boolean} enable Whether to set or remove a tab index on the element
 *     that supports keyboard focus.
 */
goog.dom.setFocusableTabIndex = function(element, enable) {
  if (enable) {
    element.tabIndex = 0;
  } else {
    // Set tabIndex to -1 first, then remove it. This is a workaround for
    // Safari (confirmed in version 4 on Windows). When removing the attribute
    // without setting it to -1 first, the element remains keyboard focusable
    // despite not having a tabIndex attribute anymore.
    element.tabIndex = -1;
    element.removeAttribute('tabIndex'); // Must be camelCase!
  }
};


/**
 * Returns the text content of the current node, without markup and invisible
 * symbols. New lines are stripped and whitespace is collapsed,
 * such that each character would be visible.
 *
 * In browsers that support it, innerText is used.  Other browsers attempt to
 * simulate it via node traversal.  Line breaks are canonicalized in IE.
 *
 * @param {Node} node The node from which we are getting content.
 * @return {string} The text content.
 */
goog.dom.getTextContent = function(node) {
  var textContent;
  // Note(arv): IE9, Opera, and Safari 3 support innerText but they include
  // text nodes in script tags. So we revert to use a user agent test here.
  if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
    textContent = goog.string.canonicalizeNewlines(node.innerText);
    // Unfortunately .innerText() returns text with &shy; symbols
    // We need to filter it out and then remove duplicate whitespaces
  } else {
    var buf = [];
    goog.dom.getTextContent_(node, buf, true);
    textContent = buf.join('');
  }

  // Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
  textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
  // Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
  textContent = textContent.replace(/\u200B/g, '');

  // Skip this replacement on old browsers with working innerText, which
  // automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
  // innerText.
  if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
    textContent = textContent.replace(/ +/g, ' ');
  }
  if (textContent != ' ') {
    textContent = textContent.replace(/^\s*/, '');
  }

  return textContent;
};


/**
 * Returns the text content of the current node, without markup.
 *
 * Unlike {@code getTextContent} this method does not collapse whitespaces
 * or normalize lines breaks.
 *
 * @param {Node} node The node from which we are getting content.
 * @return {string} The raw text content.
 */
goog.dom.getRawTextContent = function(node) {
  var buf = [];
  goog.dom.getTextContent_(node, buf, false);

  return buf.join('');
};


/**
 * Recursive support function for text content retrieval.
 *
 * @param {Node} node The node from which we are getting content.
 * @param {Array} buf string buffer.
 * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
 * @private
 */
goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
  if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
    // ignore certain tags
  } else if (node.nodeType == goog.dom.NodeType.TEXT) {
    if (normalizeWhitespace) {
      buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
    } else {
      buf.push(node.nodeValue);
    }
  } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
    buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
  } else {
    var child = node.firstChild;
    while (child) {
      goog.dom.getTextContent_(child, buf, normalizeWhitespace);
      child = child.nextSibling;
    }
  }
};


/**
 * Returns the text length of the text contained in a node, without markup. This
 * is equivalent to the selection length if the node was selected, or the number
 * of cursor movements to traverse the node. Images & BRs take one space.  New
 * lines are ignored.
 *
 * @param {Node} node The node whose text content length is being calculated.
 * @return {number} The length of {@code node}'s text content.
 */
goog.dom.getNodeTextLength = function(node) {
  return goog.dom.getTextContent(node).length;
};


/**
 * Returns the text offset of a node relative to one of its ancestors. The text
 * length is the same as the length calculated by goog.dom.getNodeTextLength.
 *
 * @param {Node} node The node whose offset is being calculated.
 * @param {Node=} opt_offsetParent The node relative to which the offset will
 *     be calculated. Defaults to the node's owner document's body.
 * @return {number} The text offset.
 */
goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
  var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
  var buf = [];
  while (node && node != root) {
    var cur = node;
    while ((cur = cur.previousSibling)) {
      buf.unshift(goog.dom.getTextContent(cur));
    }
    node = node.parentNode;
  }
  // Trim left to deal with FF cases when there might be line breaks and empty
  // nodes at the front of the text
  return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
};


/**
 * Returns the node at a given offset in a parent node.  If an object is
 * provided for the optional third parameter, the node and the remainder of the
 * offset will stored as properties of this object.
 * @param {Node} parent The parent node.
 * @param {number} offset The offset into the parent node.
 * @param {Object=} opt_result Object to be used to store the return value. The
 *     return value will be stored in the form {node: Node, remainder: number}
 *     if this object is provided.
 * @return {Node} The node at the given offset.
 */
goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
  var stack = [parent], pos = 0, cur = null;
  while (stack.length > 0 && pos < offset) {
    cur = stack.pop();
    if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
      // ignore certain tags
    } else if (cur.nodeType == goog.dom.NodeType.TEXT) {
      var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
      pos += text.length;
    } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
      pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
    } else {
      for (var i = cur.childNodes.length - 1; i >= 0; i--) {
        stack.push(cur.childNodes[i]);
      }
    }
  }
  if (goog.isObject(opt_result)) {
    opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
    opt_result.node = cur;
  }

  return cur;
};


/**
 * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
 * the object must have a numeric length property and an item function (which
 * has type 'string' on IE for some reason).
 * @param {Object} val Object to test.
 * @return {boolean} Whether the object is a NodeList.
 */
goog.dom.isNodeList = function(val) {
  // TODO(attila): Now the isNodeList is part of goog.dom we can use
  // goog.userAgent to make this simpler.
  // A NodeList must have a length property of type 'number' on all platforms.
  if (val && typeof val.length == 'number') {
    // A NodeList is an object everywhere except Safari, where it's a function.
    if (goog.isObject(val)) {
      // A NodeList must have an item function (on non-IE platforms) or an item
      // property of type 'string' (on IE).
      return typeof val.item == 'function' || typeof val.item == 'string';
    } else if (goog.isFunction(val)) {
      // On Safari, a NodeList is a function with an item property that is also
      // a function.
      return typeof val.item == 'function';
    }
  }

  // Not a NodeList.
  return false;
};


/**
 * Walks up the DOM hierarchy returning the first ancestor that has the passed
 * tag name and/or class name. If the passed element matches the specified
 * criteria, the element itself is returned.
 * @param {Node} element The DOM node to start with.
 * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
 *     null/undefined to match only based on class name).
 * @param {?string=} opt_class The class name to match (or null/undefined to
 *     match only based on tag name).
 * @return {Element} The first ancestor that matches the passed criteria, or
 *     null if no match is found.
 */
goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) {
  if (!opt_tag && !opt_class) {
    return null;
  }
  var tagName = opt_tag ? opt_tag.toUpperCase() : null;
  return /** @type {Element} */ (goog.dom.getAncestor(element,
      function(node) {
        return (!tagName || node.nodeName == tagName) &&
               (!opt_class || goog.dom.classes.has(node, opt_class));
      }, true));
};


/**
 * Walks up the DOM hierarchy returning the first ancestor that has the passed
 * class name. If the passed element matches the specified criteria, the
 * element itself is returned.
 * @param {Node} element The DOM node to start with.
 * @param {string} className The class name to match.
 * @return {Element} The first ancestor that matches the passed criteria, or
 *     null if none match.
 */
goog.dom.getAncestorByClass = function(element, className) {
  return goog.dom.getAncestorByTagNameAndClass(element, null, className);
};


/**
 * Walks up the DOM hierarchy returning the first ancestor that passes the
 * matcher function.
 * @param {Node} element The DOM node to start with.
 * @param {function(Node) : boolean} matcher A function that returns true if the
 *     passed node matches the desired criteria.
 * @param {boolean=} opt_includeNode If true, the node itself is included in
 *     the search (the first call to the matcher will pass startElement as
 *     the node to test).
 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
 *     dom.
 * @return {Node} DOM node that matched the matcher, or null if there was
 *     no match.
 */
goog.dom.getAncestor = function(
    element, matcher, opt_includeNode, opt_maxSearchSteps) {
  if (!opt_includeNode) {
    element = element.parentNode;
  }
  var ignoreSearchSteps = opt_maxSearchSteps == null;
  var steps = 0;
  while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
    if (matcher(element)) {
      return element;
    }
    element = element.parentNode;
    steps++;
  }
  // Reached the root of the DOM without a match
  return null;
};


/**
 * Determines the active element in the given document.
 * @param {Document} doc The document to look in.
 * @return {Element} The active element.
 */
goog.dom.getActiveElement = function(doc) {
  try {
    return doc && doc.activeElement;
  } catch (e) {
    // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
    // throws an exception. I'm not 100% sure why, but I suspect it chokes
    // on document.activeElement if the activeElement has been recently
    // removed from the DOM by a JS operation.
    //
    // We assume that an exception here simply means
    // "there is no active element."
  }

  return null;
};



/**
 * Create an instance of a DOM helper with a new document object.
 * @param {Document=} opt_document Document object to associate with this
 *     DOM helper.
 * @constructor
 */
goog.dom.DomHelper = function(opt_document) {
  /**
   * Reference to the document object to use
   * @type {!Document}
   * @private
   */
  this.document_ = opt_document || goog.global.document || document;
};


/**
 * Gets the dom helper object for the document where the element resides.
 * @param {Node=} opt_node If present, gets the DomHelper for this node.
 * @return {!goog.dom.DomHelper} The DomHelper.
 */
goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;


/**
 * Sets the document object.
 * @param {!Document} document Document object.
 */
goog.dom.DomHelper.prototype.setDocument = function(document) {
  this.document_ = document;
};


/**
 * Gets the document object being used by the dom library.
 * @return {!Document} Document object.
 */
goog.dom.DomHelper.prototype.getDocument = function() {
  return this.document_;
};


/**
 * Alias for {@code getElementById}. If a DOM node is passed in then we just
 * return that.
 * @param {string|Element} element Element ID or a DOM node.
 * @return {Element} The element with the given ID, or the node passed in.
 */
goog.dom.DomHelper.prototype.getElement = function(element) {
  if (goog.isString(element)) {
    return this.document_.getElementById(element);
  } else {
    return element;
  }
};


/**
 * Alias for {@code getElement}.
 * @param {string|Element} element Element ID or a DOM node.
 * @return {Element} The element with the given ID, or the node passed in.
 * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
 */
goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;


/**
 * Looks up elements by both tag and class name, using browser native functions
 * ({@code querySelectorAll}, {@code getElementsByTagName} or
 * {@code getElementsByClassName}) where possible. The returned array is a live
 * NodeList or a static list depending on the code path taken.
 *
 * @see goog.dom.query
 *
 * @param {?string=} opt_tag Element tag name or * for all tags.
 * @param {?string=} opt_class Optional class name.
 * @param {(Document|Element)=} opt_el Optional element to look in.
 * @return { {length: number} } Array-like list of elements (only a length
 *     property and numerical indices are guaranteed to exist).
 */
goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
                                                                     opt_class,
                                                                     opt_el) {
  return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
                                                opt_class, opt_el);
};


/**
 * Returns an array of all the elements with the provided className.
 * @see {goog.dom.query}
 * @param {string} className the name of the class to look for.
 * @param {Element|Document=} opt_el Optional element to look in.
 * @return { {length: number} } The items found with the class name provided.
 */
goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
  var doc = opt_el || this.document_;
  return goog.dom.getElementsByClass(className, doc);
};


/**
 * Returns the first element we find matching the provided class name.
 * @see {goog.dom.query}
 * @param {string} className the name of the class to look for.
 * @param {(Element|Document)=} opt_el Optional element to look in.
 * @return {Element} The first item found with the class name provided.
 */
goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
  var doc = opt_el || this.document_;
  return goog.dom.getElementByClass(className, doc);
};


/**
 * Alias for {@code getElementsByTagNameAndClass}.
 * @deprecated Use DomHelper getElementsByTagNameAndClass.
 * @see goog.dom.query
 *
 * @param {?string=} opt_tag Element tag name.
 * @param {?string=} opt_class Optional class name.
 * @param {Element=} opt_el Optional element to look in.
 * @return { {length: number} } Array-like list of elements (only a length
 *     property and numerical indices are guaranteed to exist).
 */
goog.dom.DomHelper.prototype.$$ =
    goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;


/**
 * Sets a number of properties on a node.
 * @param {Element} element DOM node to set properties on.
 * @param {Object} properties Hash of property:value pairs.
 */
goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;


/**
 * Gets the dimensions of the viewport.
 * @param {Window=} opt_window Optional window element to test. Defaults to
 *     the window of the Dom Helper.
 * @return {!goog.math.Size} Object with values 'width' and 'height'.
 */
goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
  // TODO(arv): This should not take an argument. That breaks the rule of a
  // a DomHelper representing a single frame/window/document.
  return goog.dom.getViewportSize(opt_window || this.getWindow());
};


/**
 * Calculates the height of the document.
 *
 * @return {number} The height of the document.
 */
goog.dom.DomHelper.prototype.getDocumentHeight = function() {
  return goog.dom.getDocumentHeight_(this.getWindow());
};


/**
 * Typedef for use with goog.dom.createDom and goog.dom.append.
 * @typedef {Object|string|Array|NodeList}
 */
goog.dom.Appendable;


/**
 * Returns a dom node with a set of attributes.  This function accepts varargs
 * for subsequent nodes to be added.  Subsequent nodes will be added to the
 * first node as childNodes.
 *
 * So:
 * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
 * would return a div with two child paragraphs
 *
 * An easy way to move all child nodes of an existing element to a new parent
 * element is:
 * <code>createDom('div', null, oldElement.childNodes);</code>
 * which will remove all child nodes from the old element and add them as
 * child nodes of the new DIV.
 *
 * @param {string} tagName Tag to create.
 * @param {Object|string=} opt_attributes If object, then a map of name-value
 *     pairs for attributes. If a string, then this is the className of the new
 *     element.
 * @param {...goog.dom.Appendable} var_args Further DOM nodes or
 *     strings for text nodes. If one of the var_args is an array or
 *     NodeList, its elements will be added as childNodes instead.
 * @return {!Element} Reference to a DOM node.
 */
goog.dom.DomHelper.prototype.createDom = function(tagName,
                                                  opt_attributes,
                                                  var_args) {
  return goog.dom.createDom_(this.document_, arguments);
};


/**
 * Alias for {@code createDom}.
 * @param {string} tagName Tag to create.
 * @param {(Object|string)=} opt_attributes If object, then a map of name-value
 *     pairs for attributes. If a string, then this is the className of the new
 *     element.
 * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
 *     text nodes.  If one of the var_args is an array, its children will be
 *     added as childNodes instead.
 * @return {!Element} Reference to a DOM node.
 * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
 */
goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;


/**
 * Creates a new element.
 * @param {string} name Tag name.
 * @return {!Element} The new element.
 */
goog.dom.DomHelper.prototype.createElement = function(name) {
  return this.document_.createElement(name);
};


/**
 * Creates a new text node.
 * @param {number|string} content Content.
 * @return {!Text} The new text node.
 */
goog.dom.DomHelper.prototype.createTextNode = function(content) {
  return this.document_.createTextNode(String(content));
};


/**
 * Create a table.
 * @param {number} rows The number of rows in the table.  Must be >= 1.
 * @param {number} columns The number of columns in the table.  Must be >= 1.
 * @param {boolean=} opt_fillWithNbsp If true, fills table entries with nsbps.
 * @return {!Element} The created table.
 */
goog.dom.DomHelper.prototype.createTable = function(rows, columns,
    opt_fillWithNbsp) {
  return goog.dom.createTable_(this.document_, rows, columns,
      !!opt_fillWithNbsp);
};


/**
 * Converts an HTML string into a node or a document fragment.  A single Node
 * is used if the {@code htmlString} only generates a single node.  If the
 * {@code htmlString} generates multiple nodes then these are put inside a
 * {@code DocumentFragment}.
 *
 * @param {string} htmlString The HTML string to convert.
 * @return {!Node} The resulting node.
 */
goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
  return goog.dom.htmlToDocumentFragment_(this.document_, htmlString);
};


/**
 * Returns the compatMode of the document.
 * @return {string} The result is either CSS1Compat or BackCompat.
 * @deprecated use goog.dom.DomHelper.prototype.isCss1CompatMode instead.
 */
goog.dom.DomHelper.prototype.getCompatMode = function() {
  return this.isCss1CompatMode() ? 'CSS1Compat' : 'BackCompat';
};


/**
 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
 * mode, false otherwise.
 * @return {boolean} True if in CSS1-compatible mode.
 */
goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
  return goog.dom.isCss1CompatMode_(this.document_);
};


/**
 * Gets the window object associated with the document.
 * @return {!Window} The window associated with the given document.
 */
goog.dom.DomHelper.prototype.getWindow = function() {
  return goog.dom.getWindow_(this.document_);
};


/**
 * Gets the document scroll element.
 * @return {Element} Scrolling element.
 */
goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
  return goog.dom.getDocumentScrollElement_(this.document_);
};


/**
 * Gets the document scroll distance as a coordinate object.
 * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
 */
goog.dom.DomHelper.prototype.getDocumentScroll = function() {
  return goog.dom.getDocumentScroll_(this.document_);
};


/**
 * Determines the active element in the given document.
 * @param {Document=} opt_doc The document to look in.
 * @return {Element} The active element.
 */
goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
  return goog.dom.getActiveElement(opt_doc || this.document_);
};


/**
 * Appends a child to a node.
 * @param {Node} parent Parent.
 * @param {Node} child Child.
 */
goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;


/**
 * Appends a node with text or other nodes.
 * @param {!Node} parent The node to append nodes to.
 * @param {...goog.dom.Appendable} var_args The things to append to the node.
 *     If this is a Node it is appended as is.
 *     If this is a string then a text node is appended.
 *     If this is an array like object then fields 0 to length - 1 are appended.
 */
goog.dom.DomHelper.prototype.append = goog.dom.append;


/**
 * Determines if the given node can contain children, intended to be used for
 * HTML generation.
 *
 * @param {Node} node The node to check.
 * @return {boolean} Whether the node can contain children.
 */
goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;


/**
 * Removes all the child nodes on a DOM node.
 * @param {Node} node Node to remove children from.
 */
goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;


/**
 * Inserts a new node before an existing reference node (i.e., as the previous
 * sibling). If the reference node has no parent, then does nothing.
 * @param {Node} newNode Node to insert.
 * @param {Node} refNode Reference node to insert before.
 */
goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;


/**
 * Inserts a new node after an existing reference node (i.e., as the next
 * sibling). If the reference node has no parent, then does nothing.
 * @param {Node} newNode Node to insert.
 * @param {Node} refNode Reference node to insert after.
 */
goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;


/**
 * Insert a child at a given index. If index is larger than the number of child
 * nodes that the parent currently has, the node is inserted as the last child
 * node.
 * @param {Element} parent The element into which to insert the child.
 * @param {Node} child The element to insert.
 * @param {number} index The index at which to insert the new child node. Must
 *     not be negative.
 */
goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;


/**
 * Removes a node from its parent.
 * @param {Node} node The node to remove.
 * @return {Node} The node removed if removed; else, null.
 */
goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;


/**
 * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
 * parent.
 * @param {Node} newNode Node to insert.
 * @param {Node} oldNode Node to replace.
 */
goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;


/**
 * Flattens an element. That is, removes it and replace it with its children.
 * @param {Element} element The element to flatten.
 * @return {Element|undefined} The original element, detached from the document
 *     tree, sans children, or undefined if the element was already not in the
 *     document.
 */
goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;


/**
 * Returns an array containing just the element children of the given element.
 * @param {Element} element The element whose element children we want.
 * @return {!(Array|NodeList)} An array or array-like list of just the element
 *     children of the given element.
 */
goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;


/**
 * Returns the first child node that is an element.
 * @param {Node} node The node to get the first child element of.
 * @return {Element} The first child node of {@code node} that is an element.
 */
goog.dom.DomHelper.prototype.getFirstElementChild =
    goog.dom.getFirstElementChild;


/**
 * Returns the last child node that is an element.
 * @param {Node} node The node to get the last child element of.
 * @return {Element} The last child node of {@code node} that is an element.
 */
goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;


/**
 * Returns the first next sibling that is an element.
 * @param {Node} node The node to get the next sibling element of.
 * @return {Element} The next sibling of {@code node} that is an element.
 */
goog.dom.DomHelper.prototype.getNextElementSibling =
    goog.dom.getNextElementSibling;


/**
 * Returns the first previous sibling that is an element.
 * @param {Node} node The node to get the previous sibling element of.
 * @return {Element} The first previous sibling of {@code node} that is
 *     an element.
 */
goog.dom.DomHelper.prototype.getPreviousElementSibling =
    goog.dom.getPreviousElementSibling;


/**
 * Returns the next node in source order from the given node.
 * @param {Node} node The node.
 * @return {Node} The next node in the DOM tree, or null if this was the last
 *     node.
 */
goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;


/**
 * Returns the previous node in source order from the given node.
 * @param {Node} node The node.
 * @return {Node} The previous node in the DOM tree, or null if this was the
 *     first node.
 */
goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;


/**
 * Whether the object looks like a DOM node.
 * @param {*} obj The object being tested for node likeness.
 * @return {boolean} Whether the object looks like a DOM node.
 */
goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;


/**
 * Whether the object looks like an Element.
 * @param {*} obj The object being tested for Element likeness.
 * @return {boolean} Whether the object looks like an Element.
 */
goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;


/**
 * Returns true if the specified value is a Window object. This includes the
 * global window for HTML pages, and iframe windows.
 * @param {*} obj Variable to test.
 * @return {boolean} Whether the variable is a window.
 */
goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;


/**
 * Returns an element's parent, if it's an Element.
 * @param {Element} element The DOM element.
 * @return {Element} The parent, or null if not an Element.
 */
goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;


/**
 * Whether a node contains another node.
 * @param {Node} parent The node that should contain the other node.
 * @param {Node} descendant The node to test presence of.
 * @return {boolean} Whether the parent node contains the descendent node.
 */
goog.dom.DomHelper.prototype.contains = goog.dom.contains;


/**
 * Compares the document order of two nodes, returning 0 if they are the same
 * node, a negative number if node1 is before node2, and a positive number if
 * node2 is before node1.  Note that we compare the order the tags appear in the
 * document so in the tree <b><i>text</i></b> the B node is considered to be
 * before the I node.
 *
 * @param {Node} node1 The first node to compare.
 * @param {Node} node2 The second node to compare.
 * @return {number} 0 if the nodes are the same node, a negative number if node1
 *     is before node2, and a positive number if node2 is before node1.
 */
goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;


/**
 * Find the deepest common ancestor of the given nodes.
 * @param {...Node} var_args The nodes to find a common ancestor of.
 * @return {Node} The common ancestor of the nodes, or null if there is none.
 *     null will only be returned if two or more of the nodes are from different
 *     documents.
 */
goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;


/**
 * Returns the owner document for a node.
 * @param {Node} node The node to get the document for.
 * @return {!Document} The document owning the node.
 */
goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;


/**
 * Cross browser function for getting the document element of an iframe.
 * @param {Element} iframe Iframe element.
 * @return {!Document} The frame content document.
 */
goog.dom.DomHelper.prototype.getFrameContentDocument =
    goog.dom.getFrameContentDocument;


/**
 * Cross browser function for getting the window of a frame or iframe.
 * @param {Element} frame Frame element.
 * @return {Window} The window associated with the given frame.
 */
goog.dom.DomHelper.prototype.getFrameContentWindow =
    goog.dom.getFrameContentWindow;


/**
 * Cross browser function for setting the text content of an element.
 * @param {Element} element The element to change the text content of.
 * @param {string} text The string that should replace the current element
 *     content with.
 */
goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;


/**
 * Gets the outerHTML of a node, which islike innerHTML, except that it
 * actually contains the HTML of the node itself.
 * @param {Element} element The element to get the HTML of.
 * @return {string} The outerHTML of the given element.
 */
goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;


/**
 * Finds the first descendant node that matches the filter function. This does
 * a depth first search.
 * @param {Node} root The root of the tree to search.
 * @param {function(Node) : boolean} p The filter function.
 * @return {Node|undefined} The found node or undefined if none is found.
 */
goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;


/**
 * Finds all the descendant nodes that matches the filter function. This does a
 * depth first search.
 * @param {Node} root The root of the tree to search.
 * @param {function(Node) : boolean} p The filter function.
 * @return {Array.<Node>} The found nodes or an empty array if none are found.
 */
goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;


/**
 * Returns true if the element has a tab index that allows it to receive
 * keyboard focus (tabIndex >= 0), false otherwise.  Note that form elements
 * natively support keyboard focus, even if they have no tab index.
 * @param {Element} element Element to check.
 * @return {boolean} Whether the element has a tab index that allows keyboard
 *     focus.
 */
goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;


/**
 * Enables or disables keyboard focus support on the element via its tab index.
 * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
 * (or elements that natively support keyboard focus, like form elements) can
 * receive keyboard focus.  See http://go/tabindex for more info.
 * @param {Element} element Element whose tab index is to be changed.
 * @param {boolean} enable Whether to set or remove a tab index on the element
 *     that supports keyboard focus.
 */
goog.dom.DomHelper.prototype.setFocusableTabIndex =
    goog.dom.setFocusableTabIndex;


/**
 * Returns the text contents of the current node, without markup. New lines are
 * stripped and whitespace is collapsed, such that each character would be
 * visible.
 *
 * In browsers that support it, innerText is used.  Other browsers attempt to
 * simulate it via node traversal.  Line breaks are canonicalized in IE.
 *
 * @param {Node} node The node from which we are getting content.
 * @return {string} The text content.
 */
goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;


/**
 * Returns the text length of the text contained in a node, without markup. This
 * is equivalent to the selection length if the node was selected, or the number
 * of cursor movements to traverse the node. Images & BRs take one space.  New
 * lines are ignored.
 *
 * @param {Node} node The node whose text content length is being calculated.
 * @return {number} The length of {@code node}'s text content.
 */
goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;


/**
 * Returns the text offset of a node relative to one of its ancestors. The text
 * length is the same as the length calculated by
 * {@code goog.dom.getNodeTextLength}.
 *
 * @param {Node} node The node whose offset is being calculated.
 * @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
 * @return {number} The text offset.
 */
goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;


/**
 * Returns the node at a given offset in a parent node.  If an object is
 * provided for the optional third parameter, the node and the remainder of the
 * offset will stored as properties of this object.
 * @param {Node} parent The parent node.
 * @param {number} offset The offset into the parent node.
 * @param {Object=} opt_result Object to be used to store the return value. The
 *     return value will be stored in the form {node: Node, remainder: number}
 *     if this object is provided.
 * @return {Node} The node at the given offset.
 */
goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;


/**
 * Returns true if the object is a {@code NodeList}.  To qualify as a NodeList,
 * the object must have a numeric length property and an item function (which
 * has type 'string' on IE for some reason).
 * @param {Object} val Object to test.
 * @return {boolean} Whether the object is a NodeList.
 */
goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;


/**
 * Walks up the DOM hierarchy returning the first ancestor that has the passed
 * tag name and/or class name. If the passed element matches the specified
 * criteria, the element itself is returned.
 * @param {Node} element The DOM node to start with.
 * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
 *     null/undefined to match only based on class name).
 * @param {?string=} opt_class The class name to match (or null/undefined to
 *     match only based on tag name).
 * @return {Element} The first ancestor that matches the passed criteria, or
 *     null if no match is found.
 */
goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
    goog.dom.getAncestorByTagNameAndClass;


/**
 * Walks up the DOM hierarchy returning the first ancestor that has the passed
 * class name. If the passed element matches the specified criteria, the
 * element itself is returned.
 * @param {Node} element The DOM node to start with.
 * @param {string} class The class name to match.
 * @return {Element} The first ancestor that matches the passed criteria, or
 *     null if none match.
 */
goog.dom.DomHelper.prototype.getAncestorByClass =
    goog.dom.getAncestorByClass;


/**
 * Walks up the DOM hierarchy returning the first ancestor that passes the
 * matcher function.
 * @param {Node} element The DOM node to start with.
 * @param {function(Node) : boolean} matcher A function that returns true if the
 *     passed node matches the desired criteria.
 * @param {boolean=} opt_includeNode If true, the node itself is included in
 *     the search (the first call to the matcher will pass startElement as
 *     the node to test).
 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
 *     dom.
 * @return {Node} DOM node that matched the matcher, or null if there was
 *     no match.
 */
goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;

// Input 159
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Vendor prefix getters.
 */

goog.provide('goog.dom.vendor');

goog.require('goog.userAgent');


/**
 * Returns the JS vendor prefix used in CSS properties. Different vendors
 * use different methods of changing the case of the property names.
 *
 * @return {?string} The JS vendor prefix or null if there is none.
 */
goog.dom.vendor.getVendorJsPrefix = function() {
  if (goog.userAgent.WEBKIT) {
    return 'Webkit';
  } else if (goog.userAgent.GECKO) {
    return 'Moz';
  } else if (goog.userAgent.IE) {
    return 'ms';
  } else if (goog.userAgent.OPERA) {
    return 'O';
  }

  return null;
};


/**
 * Returns the vendor prefix used in CSS properties.
 *
 * @return {?string} The vendor prefix or null if there is none.
 */
goog.dom.vendor.getVendorPrefix = function() {
  if (goog.userAgent.WEBKIT) {
    return '-webkit';
  } else if (goog.userAgent.GECKO) {
    return '-moz';
  } else if (goog.userAgent.IE) {
    return '-ms';
  } else if (goog.userAgent.OPERA) {
    return '-o';
  }

  return null;
};

// Input 160
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A utility class for representing a numeric box.
 */


goog.provide('goog.math.Box');

goog.require('goog.math.Coordinate');



/**
 * Class for representing a box. A box is specified as a top, right, bottom,
 * and left. A box is useful for representing margins and padding.
 *
 * @param {number} top Top.
 * @param {number} right Right.
 * @param {number} bottom Bottom.
 * @param {number} left Left.
 * @constructor
 */
goog.math.Box = function(top, right, bottom, left) {
  /**
   * Top
   * @type {number}
   */
  this.top = top;

  /**
   * Right
   * @type {number}
   */
  this.right = right;

  /**
   * Bottom
   * @type {number}
   */
  this.bottom = bottom;

  /**
   * Left
   * @type {number}
   */
  this.left = left;
};


/**
 * Creates a Box by bounding a collection of goog.math.Coordinate objects
 * @param {...goog.math.Coordinate} var_args Coordinates to be included inside
 *     the box.
 * @return {!goog.math.Box} A Box containing all the specified Coordinates.
 */
goog.math.Box.boundingBox = function(var_args) {
  var box = new goog.math.Box(arguments[0].y, arguments[0].x,
                              arguments[0].y, arguments[0].x);
  for (var i = 1; i < arguments.length; i++) {
    var coord = arguments[i];
    box.top = Math.min(box.top, coord.y);
    box.right = Math.max(box.right, coord.x);
    box.bottom = Math.max(box.bottom, coord.y);
    box.left = Math.min(box.left, coord.x);
  }
  return box;
};


/**
 * Creates a copy of the box with the same dimensions.
 * @return {!goog.math.Box} A clone of this Box.
 */
goog.math.Box.prototype.clone = function() {
  return new goog.math.Box(this.top, this.right, this.bottom, this.left);
};


if (goog.DEBUG) {
  /**
   * Returns a nice string representing the box.
   * @return {string} In the form (50t, 73r, 24b, 13l).
   * @override
   */
  goog.math.Box.prototype.toString = function() {
    return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
           this.left + 'l)';
  };
}


/**
 * Returns whether the box contains a coordinate or another box.
 *
 * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
 * @return {boolean} Whether the box contains the coordinate or other box.
 */
goog.math.Box.prototype.contains = function(other) {
  return goog.math.Box.contains(this, other);
};


/**
 * Expands box with the given margins.
 *
 * @param {number|goog.math.Box} top Top margin or box with all margins.
 * @param {number=} opt_right Right margin.
 * @param {number=} opt_bottom Bottom margin.
 * @param {number=} opt_left Left margin.
 * @return {!goog.math.Box} A reference to this Box.
 */
goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
    opt_left) {
  if (goog.isObject(top)) {
    this.top -= top.top;
    this.right += top.right;
    this.bottom += top.bottom;
    this.left -= top.left;
  } else {
    this.top -= top;
    this.right += opt_right;
    this.bottom += opt_bottom;
    this.left -= opt_left;
  }

  return this;
};


/**
 * Expand this box to include another box.
 * NOTE(user): This is used in code that needs to be very fast, please don't
 * add functionality to this function at the expense of speed (variable
 * arguments, accepting multiple argument types, etc).
 * @param {goog.math.Box} box The box to include in this one.
 */
goog.math.Box.prototype.expandToInclude = function(box) {
  this.left = Math.min(this.left, box.left);
  this.top = Math.min(this.top, box.top);
  this.right = Math.max(this.right, box.right);
  this.bottom = Math.max(this.bottom, box.bottom);
};


/**
 * Compares boxes for equality.
 * @param {goog.math.Box} a A Box.
 * @param {goog.math.Box} b A Box.
 * @return {boolean} True iff the boxes are equal, or if both are null.
 */
goog.math.Box.equals = function(a, b) {
  if (a == b) {
    return true;
  }
  if (!a || !b) {
    return false;
  }
  return a.top == b.top && a.right == b.right &&
         a.bottom == b.bottom && a.left == b.left;
};


/**
 * Returns whether a box contains a coordinate or another box.
 *
 * @param {goog.math.Box} box A Box.
 * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
 * @return {boolean} Whether the box contains the coordinate or other box.
 */
goog.math.Box.contains = function(box, other) {
  if (!box || !other) {
    return false;
  }

  if (other instanceof goog.math.Box) {
    return other.left >= box.left && other.right <= box.right &&
        other.top >= box.top && other.bottom <= box.bottom;
  }

  // other is a Coordinate.
  return other.x >= box.left && other.x <= box.right &&
         other.y >= box.top && other.y <= box.bottom;
};


/**
 * Returns the relative x position of a coordinate compared to a box.  Returns
 * zero if the coordinate is inside the box.
 *
 * @param {goog.math.Box} box A Box.
 * @param {goog.math.Coordinate} coord A Coordinate.
 * @return {number} The x position of {@code coord} relative to the nearest
 *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
 */
goog.math.Box.relativePositionX = function(box, coord) {
  if (coord.x < box.left) {
    return coord.x - box.left;
  } else if (coord.x > box.right) {
    return coord.x - box.right;
  }
  return 0;
};


/**
 * Returns the relative y position of a coordinate compared to a box.  Returns
 * zero if the coordinate is inside the box.
 *
 * @param {goog.math.Box} box A Box.
 * @param {goog.math.Coordinate} coord A Coordinate.
 * @return {number} The y position of {@code coord} relative to the nearest
 *     side of {@code box}, or zero if {@code coord} is inside {@code box}.
 */
goog.math.Box.relativePositionY = function(box, coord) {
  if (coord.y < box.top) {
    return coord.y - box.top;
  } else if (coord.y > box.bottom) {
    return coord.y - box.bottom;
  }
  return 0;
};


/**
 * Returns the distance between a coordinate and the nearest corner/side of a
 * box. Returns zero if the coordinate is inside the box.
 *
 * @param {goog.math.Box} box A Box.
 * @param {goog.math.Coordinate} coord A Coordinate.
 * @return {number} The distance between {@code coord} and the nearest
 *     corner/side of {@code box}, or zero if {@code coord} is inside
 *     {@code box}.
 */
goog.math.Box.distance = function(box, coord) {
  var x = goog.math.Box.relativePositionX(box, coord);
  var y = goog.math.Box.relativePositionY(box, coord);
  return Math.sqrt(x * x + y * y);
};


/**
 * Returns whether two boxes intersect.
 *
 * @param {goog.math.Box} a A Box.
 * @param {goog.math.Box} b A second Box.
 * @return {boolean} Whether the boxes intersect.
 */
goog.math.Box.intersects = function(a, b) {
  return (a.left <= b.right && b.left <= a.right &&
          a.top <= b.bottom && b.top <= a.bottom);
};


/**
 * Returns whether two boxes would intersect with additional padding.
 *
 * @param {goog.math.Box} a A Box.
 * @param {goog.math.Box} b A second Box.
 * @param {number} padding The additional padding.
 * @return {boolean} Whether the boxes intersect.
 */
goog.math.Box.intersectsWithPadding = function(a, b, padding) {
  return (a.left <= b.right + padding && b.left <= a.right + padding &&
          a.top <= b.bottom + padding && b.top <= a.bottom + padding);
};


/**
 * Rounds the fields to the next larger integer values.
 *
 * @return {!goog.math.Box} This box with ceil'd fields.
 */
goog.math.Box.prototype.ceil = function() {
  this.top = Math.ceil(this.top);
  this.right = Math.ceil(this.right);
  this.bottom = Math.ceil(this.bottom);
  this.left = Math.ceil(this.left);
  return this;
};


/**
 * Rounds the fields to the next smaller integer values.
 *
 * @return {!goog.math.Box} This box with floored fields.
 */
goog.math.Box.prototype.floor = function() {
  this.top = Math.floor(this.top);
  this.right = Math.floor(this.right);
  this.bottom = Math.floor(this.bottom);
  this.left = Math.floor(this.left);
  return this;
};


/**
 * Rounds the fields to nearest integer values.
 *
 * @return {!goog.math.Box} This box with rounded fields.
 */
goog.math.Box.prototype.round = function() {
  this.top = Math.round(this.top);
  this.right = Math.round(this.right);
  this.bottom = Math.round(this.bottom);
  this.left = Math.round(this.left);
  return this;
};


/**
 * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
 * is given, then the left and right values are translated by the coordinate's
 * x value and the top and bottom values are translated by the coordinate's y
 * value.  Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
 * and y dimension values.
 *
 * @param {number|goog.math.Coordinate} tx The value to translate the x
 *     dimension values by or the the coordinate to translate this box by.
 * @param {number=} opt_ty The value to translate y dimension values by.
 * @return {!goog.math.Box} This box after translating.
 */
goog.math.Box.prototype.translate = function(tx, opt_ty) {
  if (tx instanceof goog.math.Coordinate) {
    this.left += tx.x;
    this.right += tx.x;
    this.top += tx.y;
    this.bottom += tx.y;
  } else {
    this.left += tx;
    this.right += tx;
    if (goog.isNumber(opt_ty)) {
      this.top += opt_ty;
      this.bottom += opt_ty;
    }
  }
  return this;
};


/**
 * Scales this coordinate by the given scale factors. The x and y dimension
 * values are scaled by {@code sx} and {@code opt_sy} respectively.
 * If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
 *
 * @param {number} sx The scale factor to use for the x dimension.
 * @param {number=} opt_sy The scale factor to use for the y dimension.
 * @return {!goog.math.Box} This box after scaling.
 */
goog.math.Box.prototype.scale = function(sx, opt_sy) {
  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
  this.left *= sx;
  this.right *= sx;
  this.top *= sy;
  this.bottom *= sy;
  return this;
};

// Input 161
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A utility class for representing rectangles.
 */

goog.provide('goog.math.Rect');

goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Size');



/**
 * Class for representing rectangular regions.
 * @param {number} x Left.
 * @param {number} y Top.
 * @param {number} w Width.
 * @param {number} h Height.
 * @constructor
 */
goog.math.Rect = function(x, y, w, h) {
  /** @type {number} */
  this.left = x;

  /** @type {number} */
  this.top = y;

  /** @type {number} */
  this.width = w;

  /** @type {number} */
  this.height = h;
};


/**
 * @return {!goog.math.Rect} A new copy of this Rectangle.
 */
goog.math.Rect.prototype.clone = function() {
  return new goog.math.Rect(this.left, this.top, this.width, this.height);
};


/**
 * Returns a new Box object with the same position and dimensions as this
 * rectangle.
 * @return {!goog.math.Box} A new Box representation of this Rectangle.
 */
goog.math.Rect.prototype.toBox = function() {
  var right = this.left + this.width;
  var bottom = this.top + this.height;
  return new goog.math.Box(this.top,
                           right,
                           bottom,
                           this.left);
};


/**
 * Creates a new Rect object with the same position and dimensions as a given
 * Box.  Note that this is only the inverse of toBox if left/top are defined.
 * @param {goog.math.Box} box A box.
 * @return {!goog.math.Rect} A new Rect initialized with the box's position
 *     and size.
 */
goog.math.Rect.createFromBox = function(box) {
  return new goog.math.Rect(box.left, box.top,
      box.right - box.left, box.bottom - box.top);
};


if (goog.DEBUG) {
  /**
   * Returns a nice string representing size and dimensions of rectangle.
   * @return {string} In the form (50, 73 - 75w x 25h).
   * @override
   */
  goog.math.Rect.prototype.toString = function() {
    return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
           this.height + 'h)';
  };
}


/**
 * Compares rectangles for equality.
 * @param {goog.math.Rect} a A Rectangle.
 * @param {goog.math.Rect} b A Rectangle.
 * @return {boolean} True iff the rectangles have the same left, top, width,
 *     and height, or if both are null.
 */
goog.math.Rect.equals = function(a, b) {
  if (a == b) {
    return true;
  }
  if (!a || !b) {
    return false;
  }
  return a.left == b.left && a.width == b.width &&
         a.top == b.top && a.height == b.height;
};


/**
 * Computes the intersection of this rectangle and the rectangle parameter.  If
 * there is no intersection, returns false and leaves this rectangle as is.
 * @param {goog.math.Rect} rect A Rectangle.
 * @return {boolean} True iff this rectangle intersects with the parameter.
 */
goog.math.Rect.prototype.intersection = function(rect) {
  var x0 = Math.max(this.left, rect.left);
  var x1 = Math.min(this.left + this.width, rect.left + rect.width);

  if (x0 <= x1) {
    var y0 = Math.max(this.top, rect.top);
    var y1 = Math.min(this.top + this.height, rect.top + rect.height);

    if (y0 <= y1) {
      this.left = x0;
      this.top = y0;
      this.width = x1 - x0;
      this.height = y1 - y0;

      return true;
    }
  }
  return false;
};


/**
 * Returns the intersection of two rectangles. Two rectangles intersect if they
 * touch at all, for example, two zero width and height rectangles would
 * intersect if they had the same top and left.
 * @param {goog.math.Rect} a A Rectangle.
 * @param {goog.math.Rect} b A Rectangle.
 * @return {goog.math.Rect} A new intersection rect (even if width and height
 *     are 0), or null if there is no intersection.
 */
goog.math.Rect.intersection = function(a, b) {
  // There is no nice way to do intersection via a clone, because any such
  // clone might be unnecessary if this function returns null.  So, we duplicate
  // code from above.

  var x0 = Math.max(a.left, b.left);
  var x1 = Math.min(a.left + a.width, b.left + b.width);

  if (x0 <= x1) {
    var y0 = Math.max(a.top, b.top);
    var y1 = Math.min(a.top + a.height, b.top + b.height);

    if (y0 <= y1) {
      return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
    }
  }
  return null;
};


/**
 * Returns whether two rectangles intersect. Two rectangles intersect if they
 * touch at all, for example, two zero width and height rectangles would
 * intersect if they had the same top and left.
 * @param {goog.math.Rect} a A Rectangle.
 * @param {goog.math.Rect} b A Rectangle.
 * @return {boolean} Whether a and b intersect.
 */
goog.math.Rect.intersects = function(a, b) {
  return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
      a.top <= b.top + b.height && b.top <= a.top + a.height);
};


/**
 * Returns whether a rectangle intersects this rectangle.
 * @param {goog.math.Rect} rect A rectangle.
 * @return {boolean} Whether rect intersects this rectangle.
 */
goog.math.Rect.prototype.intersects = function(rect) {
  return goog.math.Rect.intersects(this, rect);
};


/**
 * Computes the difference regions between two rectangles. The return value is
 * an array of 0 to 4 rectangles defining the remaining regions of the first
 * rectangle after the second has been subtracted.
 * @param {goog.math.Rect} a A Rectangle.
 * @param {goog.math.Rect} b A Rectangle.
 * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
 *     together define the difference area of rectangle a minus rectangle b.
 */
goog.math.Rect.difference = function(a, b) {
  var intersection = goog.math.Rect.intersection(a, b);
  if (!intersection || !intersection.height || !intersection.width) {
    return [a.clone()];
  }

  var result = [];

  var top = a.top;
  var height = a.height;

  var ar = a.left + a.width;
  var ab = a.top + a.height;

  var br = b.left + b.width;
  var bb = b.top + b.height;

  // Subtract off any area on top where A extends past B
  if (b.top > a.top) {
    result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
    top = b.top;
    // If we're moving the top down, we also need to subtract the height diff.
    height -= b.top - a.top;
  }
  // Subtract off any area on bottom where A extends past B
  if (bb < ab) {
    result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
    height = bb - top;
  }
  // Subtract any area on left where A extends past B
  if (b.left > a.left) {
    result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
  }
  // Subtract any area on right where A extends past B
  if (br < ar) {
    result.push(new goog.math.Rect(br, top, ar - br, height));
  }

  return result;
};


/**
 * Computes the difference regions between this rectangle and {@code rect}. The
 * return value is an array of 0 to 4 rectangles defining the remaining regions
 * of this rectangle after the other has been subtracted.
 * @param {goog.math.Rect} rect A Rectangle.
 * @return {!Array.<!goog.math.Rect>} An array with 0 to 4 rectangles which
 *     together define the difference area of rectangle a minus rectangle b.
 */
goog.math.Rect.prototype.difference = function(rect) {
  return goog.math.Rect.difference(this, rect);
};


/**
 * Expand this rectangle to also include the area of the given rectangle.
 * @param {goog.math.Rect} rect The other rectangle.
 */
goog.math.Rect.prototype.boundingRect = function(rect) {
  // We compute right and bottom before we change left and top below.
  var right = Math.max(this.left + this.width, rect.left + rect.width);
  var bottom = Math.max(this.top + this.height, rect.top + rect.height);

  this.left = Math.min(this.left, rect.left);
  this.top = Math.min(this.top, rect.top);

  this.width = right - this.left;
  this.height = bottom - this.top;
};


/**
 * Returns a new rectangle which completely contains both input rectangles.
 * @param {goog.math.Rect} a A rectangle.
 * @param {goog.math.Rect} b A rectangle.
 * @return {goog.math.Rect} A new bounding rect, or null if either rect is
 *     null.
 */
goog.math.Rect.boundingRect = function(a, b) {
  if (!a || !b) {
    return null;
  }

  var clone = a.clone();
  clone.boundingRect(b);

  return clone;
};


/**
 * Tests whether this rectangle entirely contains another rectangle or
 * coordinate.
 *
 * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
 *     coordinate to test for containment.
 * @return {boolean} Whether this rectangle contains given rectangle or
 *     coordinate.
 */
goog.math.Rect.prototype.contains = function(another) {
  if (another instanceof goog.math.Rect) {
    return this.left <= another.left &&
           this.left + this.width >= another.left + another.width &&
           this.top <= another.top &&
           this.top + this.height >= another.top + another.height;
  } else { // (another instanceof goog.math.Coordinate)
    return another.x >= this.left &&
           another.x <= this.left + this.width &&
           another.y >= this.top &&
           another.y <= this.top + this.height;
  }
};


/**
 * @param {!goog.math.Coordinate} point A coordinate.
 * @return {number} The squared distance between the point and the closest
 *     point inside the rectangle. Returns 0 if the point is inside the
 *     rectangle.
 */
goog.math.Rect.prototype.squaredDistance = function(point) {
  var dx = point.x < this.left ?
      this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
  var dy = point.y < this.top ?
      this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
  return dx * dx + dy * dy;
};


/**
 * @param {!goog.math.Coordinate} point A coordinate.
 * @return {number} The distance between the point and the closest point
 *     inside the rectangle. Returns 0 if the point is inside the rectangle.
 */
goog.math.Rect.prototype.distance = function(point) {
  return Math.sqrt(this.squaredDistance(point));
};


/**
 * @return {!goog.math.Size} The size of this rectangle.
 */
goog.math.Rect.prototype.getSize = function() {
  return new goog.math.Size(this.width, this.height);
};


/**
 * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
 *     the rectangle.
 */
goog.math.Rect.prototype.getTopLeft = function() {
  return new goog.math.Coordinate(this.left, this.top);
};


/**
 * @return {!goog.math.Coordinate} A new coordinate for the center of the
 *     rectangle.
 */
goog.math.Rect.prototype.getCenter = function() {
  return new goog.math.Coordinate(
      this.left + this.width / 2, this.top + this.height / 2);
};


/**
 * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
 *     of the rectangle.
 */
goog.math.Rect.prototype.getBottomRight = function() {
  return new goog.math.Coordinate(
      this.left + this.width, this.top + this.height);
};


/**
 * Rounds the fields to the next larger integer values.
 * @return {!goog.math.Rect} This rectangle with ceil'd fields.
 */
goog.math.Rect.prototype.ceil = function() {
  this.left = Math.ceil(this.left);
  this.top = Math.ceil(this.top);
  this.width = Math.ceil(this.width);
  this.height = Math.ceil(this.height);
  return this;
};


/**
 * Rounds the fields to the next smaller integer values.
 * @return {!goog.math.Rect} This rectangle with floored fields.
 */
goog.math.Rect.prototype.floor = function() {
  this.left = Math.floor(this.left);
  this.top = Math.floor(this.top);
  this.width = Math.floor(this.width);
  this.height = Math.floor(this.height);
  return this;
};


/**
 * Rounds the fields to nearest integer values.
 * @return {!goog.math.Rect} This rectangle with rounded fields.
 */
goog.math.Rect.prototype.round = function() {
  this.left = Math.round(this.left);
  this.top = Math.round(this.top);
  this.width = Math.round(this.width);
  this.height = Math.round(this.height);
  return this;
};


/**
 * Translates this rectangle by the given offsets. If a
 * {@code goog.math.Coordinate} is given, then the left and top values are
 * translated by the coordinate's x and y values. Otherwise, top and left are
 * translated by {@code tx} and {@code opt_ty} respectively.
 * @param {number|goog.math.Coordinate} tx The value to translate left by or the
 *     the coordinate to translate this rect by.
 * @param {number=} opt_ty The value to translate top by.
 * @return {!goog.math.Rect} This rectangle after translating.
 */
goog.math.Rect.prototype.translate = function(tx, opt_ty) {
  if (tx instanceof goog.math.Coordinate) {
    this.left += tx.x;
    this.top += tx.y;
  } else {
    this.left += tx;
    if (goog.isNumber(opt_ty)) {
      this.top += opt_ty;
    }
  }
  return this;
};


/**
 * Scales this rectangle by the given scale factors. The left and width values
 * are scaled by {@code sx} and the top and height values are scaled by
 * {@code opt_sy}.  If {@code opt_sy} is not given, then all fields are scaled
 * by {@code sx}.
 * @param {number} sx The scale factor to use for the x dimension.
 * @param {number=} opt_sy The scale factor to use for the y dimension.
 * @return {!goog.math.Rect} This rectangle after scaling.
 */
goog.math.Rect.prototype.scale = function(sx, opt_sy) {
  var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
  this.left *= sx;
  this.width *= sx;
  this.top *= sy;
  this.height *= sy;
  return this;
};

// Input 162
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for element styles.
 *
 * @see ../demos/inline_block_quirks.html
 * @see ../demos/inline_block_standards.html
 * @see ../demos/style_viewport.html
 */

goog.provide('goog.style');


goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.vendor');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Rect');
goog.require('goog.math.Size');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.userAgent');


/**
 * @define {boolean} Whether we know at compile time that
 *     getBoundingClientRect() is present and bug-free on the browser.
 */
goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);


/**
 * Sets a style value on an element.
 *
 * This function is not indended to patch issues in the browser's style
 * handling, but to allow easy programmatic access to setting dash-separated
 * style properties.  An example is setting a batch of properties from a data
 * object without overwriting old styles.  When possible, use native APIs:
 * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
 * elem.style.cssText = 'property1: value1; property2: value2'.
 *
 * @param {Element} element The element to change.
 * @param {string|Object} style If a string, a style name. If an object, a hash
 *     of style names to style values.
 * @param {string|number|boolean=} opt_value If style was a string, then this
 *     should be the value.
 */
goog.style.setStyle = function(element, style, opt_value) {
  if (goog.isString(style)) {
    goog.style.setStyle_(element, opt_value, style);
  } else {
    goog.object.forEach(style, goog.partial(goog.style.setStyle_, element));
  }
};


/**
 * Sets a style value on an element, with parameters swapped to work with
 * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
 * necessary.
 * @param {Element} element The element to change.
 * @param {string|number|boolean|undefined} value Style value.
 * @param {string} style Style name.
 * @private
 */
goog.style.setStyle_ = function(element, value, style) {
  var propertyName = goog.style.getVendorJsStyleName_(element, style);

  if (propertyName) {
    element.style[propertyName] = value;
  }
};


/**
 * Returns the style property name in camel-case. If it does not exist and a
 * vendor-specific version of the property does exist, then return the vendor-
 * specific property name instead.
 * @param {Element} element The element to change.
 * @param {string} style Style name.
 * @return {string} Vendor-specific style.
 * @private
 */
goog.style.getVendorJsStyleName_ = function(element, style) {
  var camelStyle = goog.string.toCamelCase(style);

  if (element.style[camelStyle] === undefined) {
    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
        goog.string.toTitleCase(style);

    if (element.style[prefixedStyle] !== undefined) {
      return prefixedStyle;
    }
  }

  return camelStyle;
};


/**
 * Returns the style property name in CSS notation. If it does not exist and a
 * vendor-specific version of the property does exist, then return the vendor-
 * specific property name instead.
 * @param {Element} element The element to change.
 * @param {string} style Style name.
 * @return {string} Vendor-specific style.
 * @private
 */
goog.style.getVendorStyleName_ = function(element, style) {
  var camelStyle = goog.string.toCamelCase(style);

  if (element.style[camelStyle] === undefined) {
    var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
        goog.string.toTitleCase(style);

    if (element.style[prefixedStyle] !== undefined) {
      return goog.dom.vendor.getVendorPrefix() + '-' + style;
    }
  }

  return style;
};


/**
 * Retrieves an explicitly-set style value of a node. This returns '' if there
 * isn't a style attribute on the element or if this style property has not been
 * explicitly set in script.
 *
 * @param {Element} element Element to get style of.
 * @param {string} property Property to get, css-style (if you have a camel-case
 * property, use element.style[style]).
 * @return {string} Style value.
 */
goog.style.getStyle = function(element, property) {
  // element.style is '' for well-known properties which are unset.
  // For for browser specific styles as 'filter' is undefined
  // so we need to return '' explicitly to make it consistent across
  // browsers.
  var styleValue = element.style[goog.string.toCamelCase(property)];

  // Using typeof here because of a bug in Safari 5.1, where this value
  // was undefined, but === undefined returned false.
  if (typeof(styleValue) !== 'undefined') {
    return styleValue;
  }

  return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
      '';
};


/**
 * Retrieves a computed style value of a node. It returns empty string if the
 * value cannot be computed (which will be the case in Internet Explorer) or
 * "none" if the property requested is an SVG one and it has not been
 * explicitly set (firefox and webkit).
 *
 * @param {Element} element Element to get style of.
 * @param {string} property Property to get (camel-case).
 * @return {string} Style value.
 */
goog.style.getComputedStyle = function(element, property) {
  var doc = goog.dom.getOwnerDocument(element);
  if (doc.defaultView && doc.defaultView.getComputedStyle) {
    var styles = doc.defaultView.getComputedStyle(element, null);
    if (styles) {
      // element.style[..] is undefined for browser specific styles
      // as 'filter'.
      return styles[property] || styles.getPropertyValue(property) || '';
    }
  }

  return '';
};


/**
 * Gets the cascaded style value of a node, or null if the value cannot be
 * computed (only Internet Explorer can do this).
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (camel-case).
 * @return {string} Style value.
 */
goog.style.getCascadedStyle = function(element, style) {
  // TODO(nicksantos): This should be documented to return null. #fixTypes
  return element.currentStyle ? element.currentStyle[style] : null;
};


/**
 * Cross-browser pseudo get computed style. It returns the computed style where
 * available. If not available it tries the cascaded style value (IE
 * currentStyle) and in worst case the inline style value.  It shouldn't be
 * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
 * discussion.
 *
 * @param {Element} element Element to get style of.
 * @param {string} style Property to get (must be camelCase, not css-style.).
 * @return {string} Style value.
 * @private
 */
goog.style.getStyle_ = function(element, style) {
  return goog.style.getComputedStyle(element, style) ||
         goog.style.getCascadedStyle(element, style) ||
         (element.style && element.style[style]);
};


/**
 * Retrieves the computed value of the position CSS attribute.
 * @param {Element} element The element to get the position of.
 * @return {string} Position value.
 */
goog.style.getComputedPosition = function(element) {
  return goog.style.getStyle_(element, 'position');
};


/**
 * Retrieves the computed background color string for a given element. The
 * string returned is suitable for assigning to another element's
 * background-color, but is not guaranteed to be in any particular string
 * format. Accessing the color in a numeric form may not be possible in all
 * browsers or with all input.
 *
 * If the background color for the element is defined as a hexadecimal value,
 * the resulting string can be parsed by goog.color.parse in all supported
 * browsers.
 *
 * Whether named colors like "red" or "lightblue" get translated into a
 * format which can be parsed is browser dependent. Calling this function on
 * transparent elements will return "transparent" in most browsers or
 * "rgba(0, 0, 0, 0)" in WebKit.
 * @param {Element} element The element to get the background color of.
 * @return {string} The computed string value of the background color.
 */
goog.style.getBackgroundColor = function(element) {
  return goog.style.getStyle_(element, 'backgroundColor');
};


/**
 * Retrieves the computed value of the overflow-x CSS attribute.
 * @param {Element} element The element to get the overflow-x of.
 * @return {string} The computed string value of the overflow-x attribute.
 */
goog.style.getComputedOverflowX = function(element) {
  return goog.style.getStyle_(element, 'overflowX');
};


/**
 * Retrieves the computed value of the overflow-y CSS attribute.
 * @param {Element} element The element to get the overflow-y of.
 * @return {string} The computed string value of the overflow-y attribute.
 */
goog.style.getComputedOverflowY = function(element) {
  return goog.style.getStyle_(element, 'overflowY');
};


/**
 * Retrieves the computed value of the z-index CSS attribute.
 * @param {Element} element The element to get the z-index of.
 * @return {string|number} The computed value of the z-index attribute.
 */
goog.style.getComputedZIndex = function(element) {
  return goog.style.getStyle_(element, 'zIndex');
};


/**
 * Retrieves the computed value of the text-align CSS attribute.
 * @param {Element} element The element to get the text-align of.
 * @return {string} The computed string value of the text-align attribute.
 */
goog.style.getComputedTextAlign = function(element) {
  return goog.style.getStyle_(element, 'textAlign');
};


/**
 * Retrieves the computed value of the cursor CSS attribute.
 * @param {Element} element The element to get the cursor of.
 * @return {string} The computed string value of the cursor attribute.
 */
goog.style.getComputedCursor = function(element) {
  return goog.style.getStyle_(element, 'cursor');
};


/**
 * Sets the top/left values of an element.  If no unit is specified in the
 * argument then it will add px. The second argument is required if the first
 * argument is a string or number and is ignored if the first argument
 * is a coordinate.
 * @param {Element} el Element to move.
 * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
 * @param {string|number=} opt_arg2 Top position.
 */
goog.style.setPosition = function(el, arg1, opt_arg2) {
  var x, y;
  var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
      (goog.userAgent.MAC || goog.userAgent.X11) &&
      goog.userAgent.isVersionOrHigher('1.9');

  if (arg1 instanceof goog.math.Coordinate) {
    x = arg1.x;
    y = arg1.y;
  } else {
    x = arg1;
    y = opt_arg2;
  }

  // Round to the nearest pixel for buggy sub-pixel support.
  el.style.left = goog.style.getPixelStyleValue_(
      /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
  el.style.top = goog.style.getPixelStyleValue_(
      /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
};


/**
 * Gets the offsetLeft and offsetTop properties of an element and returns them
 * in a Coordinate object
 * @param {Element} element Element.
 * @return {!goog.math.Coordinate} The position.
 */
goog.style.getPosition = function(element) {
  return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
};


/**
 * Returns the viewport element for a particular document
 * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
 *     of.
 * @return {Element} document.documentElement or document.body.
 */
goog.style.getClientViewportElement = function(opt_node) {
  var doc;
  if (opt_node) {
    doc = goog.dom.getOwnerDocument(opt_node);
  } else {
    doc = goog.dom.getDocument();
  }

  // In old IE versions the document.body represented the viewport
  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
      !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
    return doc.body;
  }
  return doc.documentElement;
};


/**
 * Calculates the viewport coordinates relative to the page/document
 * containing the node. The viewport may be the browser viewport for
 * non-iframe document, or the iframe container for iframe'd document.
 * @param {!Document} doc The document to use as the reference point.
 * @return {!goog.math.Coordinate} The page offset of the viewport.
 */
goog.style.getViewportPageOffset = function(doc) {
  var body = doc.body;
  var documentElement = doc.documentElement;
  var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
  var scrollTop = body.scrollTop || documentElement.scrollTop;
  return new goog.math.Coordinate(scrollLeft, scrollTop);
};


/**
 * Gets the client rectangle of the DOM element.
 *
 * getBoundingClientRect is part of a new CSS object model draft (with a
 * long-time presence in IE), replacing the error-prone parent offset
 * computation and the now-deprecated Gecko getBoxObjectFor.
 *
 * This utility patches common browser bugs in getBoundingClientRect. It
 * will fail if getBoundingClientRect is unsupported.
 *
 * If the element is not in the DOM, the result is undefined, and an error may
 * be thrown depending on user agent.
 *
 * @param {!Element} el The element whose bounding rectangle is being queried.
 * @return {Object} A native bounding rectangle with numerical left, top,
 *     right, and bottom.  Reported by Firefox to be of object type ClientRect.
 * @private
 */
goog.style.getBoundingClientRect_ = function(el) {
  var rect = el.getBoundingClientRect();
  // Patch the result in IE only, so that this function can be inlined if
  // compiled for non-IE.
  if (goog.userAgent.IE) {

    // In IE, most of the time, 2 extra pixels are added to the top and left
    // due to the implicit 2-pixel inset border.  In IE6/7 quirks mode and
    // IE6 standards mode, this border can be overridden by setting the
    // document element's border to zero -- thus, we cannot rely on the
    // offset always being 2 pixels.

    // In quirks mode, the offset can be determined by querying the body's
    // clientLeft/clientTop, but in standards mode, it is found by querying
    // the document element's clientLeft/clientTop.  Since we already called
    // getBoundingClientRect we have already forced a reflow, so it is not
    // too expensive just to query them all.

    // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
    var doc = el.ownerDocument;
    rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
    rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
  }
  return /** @type {Object} */ (rect);
};


/**
 * Returns the first parent that could affect the position of a given element.
 * @param {Element} element The element to get the offset parent for.
 * @return {Element} The first offset parent or null if one cannot be found.
 */
goog.style.getOffsetParent = function(element) {
  // element.offsetParent does the right thing in IE7 and below.  In other
  // browsers it only includes elements with position absolute, relative or
  // fixed, not elements with overflow set to auto or scroll.
  if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
    return element.offsetParent;
  }

  var doc = goog.dom.getOwnerDocument(element);
  var positionStyle = goog.style.getStyle_(element, 'position');
  var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
  for (var parent = element.parentNode; parent && parent != doc;
       parent = parent.parentNode) {
    positionStyle =
        goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
    skipStatic = skipStatic && positionStyle == 'static' &&
                 parent != doc.documentElement && parent != doc.body;
    if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
                        parent.scrollHeight > parent.clientHeight ||
                        positionStyle == 'fixed' ||
                        positionStyle == 'absolute' ||
                        positionStyle == 'relative')) {
      return /** @type {!Element} */ (parent);
    }
  }
  return null;
};


/**
 * Calculates and returns the visible rectangle for a given element. Returns a
 * box describing the visible portion of the nearest scrollable offset ancestor.
 * Coordinates are given relative to the document.
 *
 * @param {Element} element Element to get the visible rect for.
 * @return {goog.math.Box} Bounding elementBox describing the visible rect or
 *     null if scrollable ancestor isn't inside the visible viewport.
 */
goog.style.getVisibleRectForElement = function(element) {
  var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
  var dom = goog.dom.getDomHelper(element);
  var body = dom.getDocument().body;
  var documentElement = dom.getDocument().documentElement;
  var scrollEl = dom.getDocumentScrollElement();

  // Determine the size of the visible rect by climbing the dom accounting for
  // all scrollable containers.
  for (var el = element; el = goog.style.getOffsetParent(el); ) {
    // clientWidth is zero for inline block elements in IE.
    // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
    if ((!goog.userAgent.IE || el.clientWidth != 0) &&
        (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
        // body may have overflow set on it, yet we still get the entire
        // viewport. In some browsers, el.offsetParent may be
        // document.documentElement, so check for that too.
        (el != body && el != documentElement &&
            goog.style.getStyle_(el, 'overflow') != 'visible')) {
      var pos = goog.style.getPageOffset(el);
      var client = goog.style.getClientLeftTop(el);
      pos.x += client.x;
      pos.y += client.y;

      visibleRect.top = Math.max(visibleRect.top, pos.y);
      visibleRect.right = Math.min(visibleRect.right,
                                   pos.x + el.clientWidth);
      visibleRect.bottom = Math.min(visibleRect.bottom,
                                    pos.y + el.clientHeight);
      visibleRect.left = Math.max(visibleRect.left, pos.x);
    }
  }

  // Clip by window's viewport.
  var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
  visibleRect.left = Math.max(visibleRect.left, scrollX);
  visibleRect.top = Math.max(visibleRect.top, scrollY);
  var winSize = dom.getViewportSize();
  visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
  visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
  return visibleRect.top >= 0 && visibleRect.left >= 0 &&
         visibleRect.bottom > visibleRect.top &&
         visibleRect.right > visibleRect.left ?
         visibleRect : null;
};


/**
 * Calculate the scroll position of {@code container} with the minimum amount so
 * that the content and the borders of the given {@code element} become visible.
 * If the element is bigger than the container, its top left corner will be
 * aligned as close to the container's top left corner as possible.
 *
 * @param {Element} element The element to make visible.
 * @param {Element} container The container to scroll.
 * @param {boolean=} opt_center Whether to center the element in the container.
 *     Defaults to false.
 * @return {!goog.math.Coordinate} The new scroll position of the container,
 *     in form of goog.math.Coordinate(scrollLeft, scrollTop).
 */
goog.style.getContainerOffsetToScrollInto =
    function(element, container, opt_center) {
  // Absolute position of the element's border's top left corner.
  var elementPos = goog.style.getPageOffset(element);
  // Absolute position of the container's border's top left corner.
  var containerPos = goog.style.getPageOffset(container);
  var containerBorder = goog.style.getBorderBox(container);
  // Relative pos. of the element's border box to the container's content box.
  var relX = elementPos.x - containerPos.x - containerBorder.left;
  var relY = elementPos.y - containerPos.y - containerBorder.top;
  // How much the element can move in the container, i.e. the difference between
  // the element's bottom-right-most and top-left-most position where it's
  // fully visible.
  var spaceX = container.clientWidth - element.offsetWidth;
  var spaceY = container.clientHeight - element.offsetHeight;

  var scrollLeft = container.scrollLeft;
  var scrollTop = container.scrollTop;
  if (opt_center) {
    // All browsers round non-integer scroll positions down.
    scrollLeft += relX - spaceX / 2;
    scrollTop += relY - spaceY / 2;
  } else {
    // This formula was designed to give the correct scroll values in the
    // following cases:
    // - element is higher than container (spaceY < 0) => scroll down by relY
    // - element is not higher that container (spaceY >= 0):
    //   - it is above container (relY < 0) => scroll up by abs(relY)
    //   - it is below container (relY > spaceY) => scroll down by relY - spaceY
    //   - it is in the container => don't scroll
    scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
    scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
  }
  return new goog.math.Coordinate(scrollLeft, scrollTop);
};


/**
 * Changes the scroll position of {@code container} with the minimum amount so
 * that the content and the borders of the given {@code element} become visible.
 * If the element is bigger than the container, its top left corner will be
 * aligned as close to the container's top left corner as possible.
 *
 * @param {Element} element The element to make visible.
 * @param {Element} container The container to scroll.
 * @param {boolean=} opt_center Whether to center the element in the container.
 *     Defaults to false.
 */
goog.style.scrollIntoContainerView = function(element, container, opt_center) {
  var offset =
      goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
  container.scrollLeft = offset.x;
  container.scrollTop = offset.y;
};


/**
 * Returns clientLeft (width of the left border and, if the directionality is
 * right to left, the vertical scrollbar) and clientTop as a coordinate object.
 *
 * @param {Element} el Element to get clientLeft for.
 * @return {!goog.math.Coordinate} Client left and top.
 */
goog.style.getClientLeftTop = function(el) {
  // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
  // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
    var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
    if (goog.style.isRightToLeft(el)) {
      var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
          parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
      left += scrollbarWidth;
    }
    return new goog.math.Coordinate(left,
        parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
  }

  return new goog.math.Coordinate(el.clientLeft, el.clientTop);
};


/**
 * Returns a Coordinate object relative to the top-left of the HTML document.
 * Implemented as a single function to save having to do two recursive loops in
 * opera and safari just to get both coordinates.  If you just want one value do
 * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
 * note if you call both those methods the tree will be analysed twice.
 *
 * @param {Element} el Element to get the page offset for.
 * @return {!goog.math.Coordinate} The page offset.
 */
goog.style.getPageOffset = function(el) {
  var box, doc = goog.dom.getOwnerDocument(el);
  var positionStyle = goog.style.getStyle_(el, 'position');
  // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
  goog.asserts.assertObject(el, 'Parameter is required');

  // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
  // position. When invoked for an element with position absolute and a negative
  // position though it can be off by one. Therefor the recursive implementation
  // is used in those (relatively rare) cases.
  var BUGGY_GECKO_BOX_OBJECT =
      !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
      goog.userAgent.GECKO && doc.getBoxObjectFor &&
      !el.getBoundingClientRect && positionStyle == 'absolute' &&
      (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);

  // NOTE(arv): If element is hidden (display none or disconnected or any the
  // ancestors are hidden) we get (0,0) by default but we still do the
  // accumulation of scroll position.

  // TODO(arv): Should we check if the node is disconnected and in that case
  //            return (0,0)?

  var pos = new goog.math.Coordinate(0, 0);
  var viewportElement = goog.style.getClientViewportElement(doc);
  if (el == viewportElement) {
    // viewport is always at 0,0 as that defined the coordinate system for this
    // function - this avoids special case checks in the code below
    return pos;
  }

  // IE, Gecko 1.9+, and most modern WebKit.
  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
      el.getBoundingClientRect) {
    box = goog.style.getBoundingClientRect_(el);
    // Must add the scroll coordinates in to get the absolute page offset
    // of element since getBoundingClientRect returns relative coordinates to
    // the viewport.
    var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
    pos.x = box.left + scrollCoord.x;
    pos.y = box.top + scrollCoord.y;

  // Gecko prior to 1.9.
  } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
    // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
    // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
    // https://bugzilla.mozilla.org/show_bug.cgi?id=330619

    box = doc.getBoxObjectFor(el);
    // TODO(user): Fix the off-by-one error when window is scrolled down
    // or right more than 1 pixel. The viewport offset does not move in lock
    // step with the window scroll; it moves in increments of 2px and at
    // somewhat random intervals.
    var vpBox = doc.getBoxObjectFor(viewportElement);
    pos.x = box.screenX - vpBox.screenX;
    pos.y = box.screenY - vpBox.screenY;

  // Safari, Opera and Camino up to 1.0.4.
  } else {
    var parent = el;
    do {
      pos.x += parent.offsetLeft;
      pos.y += parent.offsetTop;
      // For safari/chrome, we need to add parent's clientLeft/Top as well.
      if (parent != el) {
        pos.x += parent.clientLeft || 0;
        pos.y += parent.clientTop || 0;
      }
      // In Safari when hit a position fixed element the rest of the offsets
      // are not correct.
      if (goog.userAgent.WEBKIT &&
          goog.style.getComputedPosition(parent) == 'fixed') {
        pos.x += doc.body.scrollLeft;
        pos.y += doc.body.scrollTop;
        break;
      }
      parent = parent.offsetParent;
    } while (parent && parent != el);

    // Opera & (safari absolute) incorrectly account for body offsetTop.
    if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
        positionStyle == 'absolute')) {
      pos.y -= doc.body.offsetTop;
    }

    for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
        parent != doc.body && parent != viewportElement; ) {
      pos.x -= parent.scrollLeft;
      // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
      // report an invalid scroll top value. The bug was fixed in Opera 9.5
      // however as that version supports getBoundingClientRect it won't
      // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
      if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
        pos.y -= parent.scrollTop;
      }
    }
  }

  return pos;
};


/**
 * Returns the left coordinate of an element relative to the HTML document
 * @param {Element} el Elements.
 * @return {number} The left coordinate.
 */
goog.style.getPageOffsetLeft = function(el) {
  return goog.style.getPageOffset(el).x;
};


/**
 * Returns the top coordinate of an element relative to the HTML document
 * @param {Element} el Elements.
 * @return {number} The top coordinate.
 */
goog.style.getPageOffsetTop = function(el) {
  return goog.style.getPageOffset(el).y;
};


/**
 * Returns a Coordinate object relative to the top-left of an HTML document
 * in an ancestor frame of this element. Used for measuring the position of
 * an element inside a frame relative to a containing frame.
 *
 * @param {Element} el Element to get the page offset for.
 * @param {Window} relativeWin The window to measure relative to. If relativeWin
 *     is not in the ancestor frame chain of the element, we measure relative to
 *     the top-most window.
 * @return {!goog.math.Coordinate} The page offset.
 */
goog.style.getFramedPageOffset = function(el, relativeWin) {
  var position = new goog.math.Coordinate(0, 0);

  // Iterate up the ancestor frame chain, keeping track of the current window
  // and the current element in that window.
  var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
  var currentEl = el;
  do {
    // if we're at the top window, we want to get the page offset.
    // if we're at an inner frame, we only want to get the window position
    // so that we can determine the actual page offset in the context of
    // the outer window.
    var offset = currentWin == relativeWin ?
        goog.style.getPageOffset(currentEl) :
        goog.style.getClientPositionForElement_(
            goog.asserts.assert(currentEl));

    position.x += offset.x;
    position.y += offset.y;
  } while (currentWin && currentWin != relativeWin &&
      (currentEl = currentWin.frameElement) &&
      (currentWin = currentWin.parent));

  return position;
};


/**
 * Translates the specified rect relative to origBase page, for newBase page.
 * If origBase and newBase are the same, this function does nothing.
 *
 * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
 *     and it will have the translated result.
 * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
 * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
 *     coordinate.  This must be a DOM for an ancestor frame of origBase
 *     or the same as origBase.
 */
goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
  if (origBase.getDocument() != newBase.getDocument()) {
    var body = origBase.getDocument().body;
    var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());

    // Adjust Body's margin.
    pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));

    if (goog.userAgent.IE && !origBase.isCss1CompatMode()) {
      pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
    }

    rect.left += pos.x;
    rect.top += pos.y;
  }
};


/**
 * Returns the position of an element relative to another element in the
 * document.  A relative to B
 * @param {Element|Event|goog.events.Event} a Element or mouse event whose
 *     position we're calculating.
 * @param {Element|Event|goog.events.Event} b Element or mouse event position
 *     is relative to.
 * @return {!goog.math.Coordinate} The relative position.
 */
goog.style.getRelativePosition = function(a, b) {
  var ap = goog.style.getClientPosition(a);
  var bp = goog.style.getClientPosition(b);
  return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
};


/**
 * Returns the position of the event or the element's border box relative to
 * the client viewport.
 * @param {!Element} el Element whose position to get.
 * @return {!goog.math.Coordinate} The position.
 * @private
 */
goog.style.getClientPositionForElement_ = function(el) {
  var pos;
  if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
      el.getBoundingClientRect) {
    // IE, Gecko 1.9+, and most modern WebKit
    var box = goog.style.getBoundingClientRect_(el);
    pos = new goog.math.Coordinate(box.left, box.top);
  } else {
    var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
    var pageCoord = goog.style.getPageOffset(el);
    pos = new goog.math.Coordinate(
        pageCoord.x - scrollCoord.x,
        pageCoord.y - scrollCoord.y);
  }

  // Gecko below version 12 doesn't add CSS translation to the client position
  // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
  // so manually.
  if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
    return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
  } else {
    return pos;
  }
};


/**
 * Returns the position of the event or the element's border box relative to
 * the client viewport.
 * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
 * @return {!goog.math.Coordinate} The position.
 */
goog.style.getClientPosition = function(el) {
  goog.asserts.assert(el);
  if (el.nodeType == goog.dom.NodeType.ELEMENT) {
    return goog.style.getClientPositionForElement_(
        /** @type {!Element} */ (el));
  } else {
    var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
    var targetEvent = el;

    if (el.targetTouches) {
      targetEvent = el.targetTouches[0];
    } else if (isAbstractedEvent && el.getBrowserEvent().targetTouches) {
      targetEvent = el.getBrowserEvent().targetTouches[0];
    }

    return new goog.math.Coordinate(
        targetEvent.clientX,
        targetEvent.clientY);
  }
};


/**
 * Moves an element to the given coordinates relative to the client viewport.
 * @param {Element} el Absolutely positioned element to set page offset for.
 *     It must be in the document.
 * @param {number|goog.math.Coordinate} x Left position of the element's margin
 *     box or a coordinate object.
 * @param {number=} opt_y Top position of the element's margin box.
 */
goog.style.setPageOffset = function(el, x, opt_y) {
  // Get current pageoffset
  var cur = goog.style.getPageOffset(el);

  if (x instanceof goog.math.Coordinate) {
    opt_y = x.y;
    x = x.x;
  }

  // NOTE(arv): We cannot allow strings for x and y. We could but that would
  // require us to manually transform between different units

  // Work out deltas
  var dx = x - cur.x;
  var dy = opt_y - cur.y;

  // Set position to current left/top + delta
  goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
};


/**
 * Sets the width/height values of an element.  If an argument is numeric,
 * or a goog.math.Size is passed, it is assumed to be pixels and will add
 * 'px' after converting it to an integer in string form. (This just sets the
 * CSS width and height properties so it might set content-box or border-box
 * size depending on the box model the browser is using.)
 *
 * @param {Element} element Element to set the size of.
 * @param {string|number|goog.math.Size} w Width of the element, or a
 *     size object.
 * @param {string|number=} opt_h Height of the element. Required if w is not a
 *     size object.
 */
goog.style.setSize = function(element, w, opt_h) {
  var h;
  if (w instanceof goog.math.Size) {
    h = w.height;
    w = w.width;
  } else {
    if (opt_h == undefined) {
      throw Error('missing height argument');
    }
    h = opt_h;
  }

  goog.style.setWidth(element, /** @type {string|number} */ (w));
  goog.style.setHeight(element, /** @type {string|number} */ (h));
};


/**
 * Helper function to create a string to be set into a pixel-value style
 * property of an element. Can round to the nearest integer value.
 *
 * @param {string|number} value The style value to be used. If a number,
 *     'px' will be appended, otherwise the value will be applied directly.
 * @param {boolean} round Whether to round the nearest integer (if property
 *     is a number).
 * @return {string} The string value for the property.
 * @private
 */
goog.style.getPixelStyleValue_ = function(value, round) {
  if (typeof value == 'number') {
    value = (round ? Math.round(value) : value) + 'px';
  }

  return value;
};


/**
 * Set the height of an element.  Sets the element's style property.
 * @param {Element} element Element to set the height of.
 * @param {string|number} height The height value to set.  If a number, 'px'
 *     will be appended, otherwise the value will be applied directly.
 */
goog.style.setHeight = function(element, height) {
  element.style.height = goog.style.getPixelStyleValue_(height, true);
};


/**
 * Set the width of an element.  Sets the element's style property.
 * @param {Element} element Element to set the width of.
 * @param {string|number} width The width value to set.  If a number, 'px'
 *     will be appended, otherwise the value will be applied directly.
 */
goog.style.setWidth = function(element, width) {
  element.style.width = goog.style.getPixelStyleValue_(width, true);
};


/**
 * Gets the height and width of an element, even if its display is none.
 * Specifically, this returns the height and width of the border box,
 * irrespective of the box model in effect.
 * @param {Element} element Element to get size of.
 * @return {!goog.math.Size} Object with width/height properties.
 */
goog.style.getSize = function(element) {
  if (goog.style.getStyle_(element, 'display') != 'none') {
    return goog.style.getSizeWithDisplay_(element);
  }

  var style = element.style;
  var originalDisplay = style.display;
  var originalVisibility = style.visibility;
  var originalPosition = style.position;

  style.visibility = 'hidden';
  style.position = 'absolute';
  style.display = 'inline';

  var size = goog.style.getSizeWithDisplay_(element);

  style.display = originalDisplay;
  style.position = originalPosition;
  style.visibility = originalVisibility;

  return size;
};


/**
 * Gets the height and with of an element when the display is not none.
 * @param {Element} element Element to get size of.
 * @return {!goog.math.Size} Object with width/height properties.
 * @private
 */
goog.style.getSizeWithDisplay_ = function(element) {
  var offsetWidth = element.offsetWidth;
  var offsetHeight = element.offsetHeight;
  var webkitOffsetsZero =
      goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
  if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
      element.getBoundingClientRect) {
    // Fall back to calling getBoundingClientRect when offsetWidth or
    // offsetHeight are not defined, or when they are zero in WebKit browsers.
    // This makes sure that we return for the correct size for SVG elements, but
    // will still return 0 on Webkit prior to 534.8, see
    // http://trac.webkit.org/changeset/67252.
    var clientRect = goog.style.getBoundingClientRect_(element);
    return new goog.math.Size(clientRect.right - clientRect.left,
        clientRect.bottom - clientRect.top);
  }
  return new goog.math.Size(offsetWidth, offsetHeight);
};


/**
 * Returns a bounding rectangle for a given element in page space.
 * @param {Element} element Element to get bounds of. Must not be display none.
 * @return {!goog.math.Rect} Bounding rectangle for the element.
 */
goog.style.getBounds = function(element) {
  var o = goog.style.getPageOffset(element);
  var s = goog.style.getSize(element);
  return new goog.math.Rect(o.x, o.y, s.width, s.height);
};


/**
 * Converts a CSS selector in the form style-property to styleProperty.
 * @param {*} selector CSS Selector.
 * @return {string} Camel case selector.
 * @deprecated Use goog.string.toCamelCase instead.
 */
goog.style.toCamelCase = function(selector) {
  return goog.string.toCamelCase(String(selector));
};


/**
 * Converts a CSS selector in the form styleProperty to style-property.
 * @param {string} selector Camel case selector.
 * @return {string} Selector cased.
 * @deprecated Use goog.string.toSelectorCase instead.
 */
goog.style.toSelectorCase = function(selector) {
  return goog.string.toSelectorCase(selector);
};


/**
 * Gets the opacity of a node (x-browser). This gets the inline style opacity
 * of the node, and does not take into account the cascaded or the computed
 * style for this node.
 * @param {Element} el Element whose opacity has to be found.
 * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
 *     if the opacity is not set.
 */
goog.style.getOpacity = function(el) {
  var style = el.style;
  var result = '';
  if ('opacity' in style) {
    result = style.opacity;
  } else if ('MozOpacity' in style) {
    result = style.MozOpacity;
  } else if ('filter' in style) {
    var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
    if (match) {
      result = String(match[1] / 100);
    }
  }
  return result == '' ? result : Number(result);
};


/**
 * Sets the opacity of a node (x-browser).
 * @param {Element} el Elements whose opacity has to be set.
 * @param {number|string} alpha Opacity between 0 and 1 or an empty string
 *     {@code ''} to clear the opacity.
 */
goog.style.setOpacity = function(el, alpha) {
  var style = el.style;
  if ('opacity' in style) {
    style.opacity = alpha;
  } else if ('MozOpacity' in style) {
    style.MozOpacity = alpha;
  } else if ('filter' in style) {
    // TODO(arv): Overwriting the filter might have undesired side effects.
    if (alpha === '') {
      style.filter = '';
    } else {
      style.filter = 'alpha(opacity=' + alpha * 100 + ')';
    }
  }
};


/**
 * Sets the background of an element to a transparent image in a browser-
 * independent manner.
 *
 * This function does not support repeating backgrounds or alternate background
 * positions to match the behavior of Internet Explorer. It also does not
 * support sizingMethods other than crop since they cannot be replicated in
 * browsers other than Internet Explorer.
 *
 * @param {Element} el The element to set background on.
 * @param {string} src The image source URL.
 */
goog.style.setTransparentBackgroundImage = function(el, src) {
  var style = el.style;
  // It is safe to use the style.filter in IE only. In Safari 'filter' is in
  // style object but access to style.filter causes it to throw an exception.
  // Note: IE8 supports images with an alpha channel.
  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    // See TODO in setOpacity.
    style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
        'src="' + src + '", sizingMethod="crop")';
  } else {
    // Set style properties individually instead of using background shorthand
    // to prevent overwriting a pre-existing background color.
    style.backgroundImage = 'url(' + src + ')';
    style.backgroundPosition = 'top left';
    style.backgroundRepeat = 'no-repeat';
  }
};


/**
 * Clears the background image of an element in a browser independent manner.
 * @param {Element} el The element to clear background image for.
 */
goog.style.clearTransparentBackgroundImage = function(el) {
  var style = el.style;
  if ('filter' in style) {
    // See TODO in setOpacity.
    style.filter = '';
  } else {
    // Set style properties individually instead of using background shorthand
    // to prevent overwriting a pre-existing background color.
    style.backgroundImage = 'none';
  }
};


/**
 * Shows or hides an element from the page. Hiding the element is done by
 * setting the display property to "none", removing the element from the
 * rendering hierarchy so it takes up no space. To show the element, the default
 * inherited display property is restored (defined either in stylesheets or by
 * the browser's default style rules.)
 *
 * Caveat 1: if the inherited display property for the element is set to "none"
 * by the stylesheets, that is the property that will be restored by a call to
 * showElement(), effectively toggling the display between "none" and "none".
 *
 * Caveat 2: if the element display style is set inline (by setting either
 * element.style.display or a style attribute in the HTML), a call to
 * showElement will clear that setting and defer to the inherited style in the
 * stylesheet.
 * @param {Element} el Element to show or hide.
 * @param {*} display True to render the element in its default style,
 *     false to disable rendering the element.
 * @deprecated Use goog.style.setElementShown instead.
 */
goog.style.showElement = function(el, display) {
  goog.style.setElementShown(el, display);
};


/**
 * Shows or hides an element from the page. Hiding the element is done by
 * setting the display property to "none", removing the element from the
 * rendering hierarchy so it takes up no space. To show the element, the default
 * inherited display property is restored (defined either in stylesheets or by
 * the browser's default style rules).
 *
 * Caveat 1: if the inherited display property for the element is set to "none"
 * by the stylesheets, that is the property that will be restored by a call to
 * setElementShown(), effectively toggling the display between "none" and
 * "none".
 *
 * Caveat 2: if the element display style is set inline (by setting either
 * element.style.display or a style attribute in the HTML), a call to
 * setElementShown will clear that setting and defer to the inherited style in
 * the stylesheet.
 * @param {Element} el Element to show or hide.
 * @param {*} isShown True to render the element in its default style,
 *     false to disable rendering the element.
 */
goog.style.setElementShown = function(el, isShown) {
  el.style.display = isShown ? '' : 'none';
};


/**
 * Test whether the given element has been shown or hidden via a call to
 * {@link #setElementShown}.
 *
 * Note this is strictly a companion method for a call
 * to {@link #setElementShown} and the same caveats apply; in particular, this
 * method does not guarantee that the return value will be consistent with
 * whether or not the element is actually visible.
 *
 * @param {Element} el The element to test.
 * @return {boolean} Whether the element has been shown.
 * @see #setElementShown
 */
goog.style.isElementShown = function(el) {
  return el.style.display != 'none';
};


/**
 * Installs the styles string into the window that contains opt_element.  If
 * opt_element is null, the main window is used.
 * @param {string} stylesString The style string to install.
 * @param {Node=} opt_node Node whose parent document should have the
 *     styles installed.
 * @return {Element|StyleSheet} The style element created.
 */
goog.style.installStyles = function(stylesString, opt_node) {
  var dh = goog.dom.getDomHelper(opt_node);
  var styleSheet = null;

  if (goog.userAgent.IE) {
    styleSheet = dh.getDocument().createStyleSheet();
    goog.style.setStyles(styleSheet, stylesString);
  } else {
    var head = dh.getElementsByTagNameAndClass('head')[0];

    // In opera documents are not guaranteed to have a head element, thus we
    // have to make sure one exists before using it.
    if (!head) {
      var body = dh.getElementsByTagNameAndClass('body')[0];
      head = dh.createDom('head');
      body.parentNode.insertBefore(head, body);
    }
    styleSheet = dh.createDom('style');
    // NOTE(user): Setting styles after the style element has been appended
    // to the head results in a nasty Webkit bug in certain scenarios. Please
    // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
    // details.
    goog.style.setStyles(styleSheet, stylesString);
    dh.appendChild(head, styleSheet);
  }
  return styleSheet;
};


/**
 * Removes the styles added by {@link #installStyles}.
 * @param {Element|StyleSheet} styleSheet The value returned by
 *     {@link #installStyles}.
 */
goog.style.uninstallStyles = function(styleSheet) {
  var node = styleSheet.ownerNode || styleSheet.owningElement ||
      /** @type {Element} */ (styleSheet);
  goog.dom.removeNode(node);
};


/**
 * Sets the content of a style element.  The style element can be any valid
 * style element.  This element will have its content completely replaced by
 * the new stylesString.
 * @param {Element|StyleSheet} element A stylesheet element as returned by
 *     installStyles.
 * @param {string} stylesString The new content of the stylesheet.
 */
goog.style.setStyles = function(element, stylesString) {
  if (goog.userAgent.IE) {
    // Adding the selectors individually caused the browser to hang if the
    // selector was invalid or there were CSS comments.  Setting the cssText of
    // the style node works fine and ignores CSS that IE doesn't understand
    element.cssText = stylesString;
  } else {
    element.innerHTML = stylesString;
  }
};


/**
 * Sets 'white-space: pre-wrap' for a node (x-browser).
 *
 * There are as many ways of specifying pre-wrap as there are browsers.
 *
 * CSS3/IE8: white-space: pre-wrap;
 * Mozilla:  white-space: -moz-pre-wrap;
 * Opera:    white-space: -o-pre-wrap;
 * IE6/7:    white-space: pre; word-wrap: break-word;
 *
 * @param {Element} el Element to enable pre-wrap for.
 */
goog.style.setPreWrap = function(el) {
  var style = el.style;
  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    style.whiteSpace = 'pre';
    style.wordWrap = 'break-word';
  } else if (goog.userAgent.GECKO) {
    style.whiteSpace = '-moz-pre-wrap';
  } else {
    style.whiteSpace = 'pre-wrap';
  }
};


/**
 * Sets 'display: inline-block' for an element (cross-browser).
 * @param {Element} el Element to which the inline-block display style is to be
 *    applied.
 * @see ../demos/inline_block_quirks.html
 * @see ../demos/inline_block_standards.html
 */
goog.style.setInlineBlock = function(el) {
  var style = el.style;
  // Without position:relative, weirdness ensues.  Just accept it and move on.
  style.position = 'relative';

  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    // IE8 supports inline-block so fall through to the else
    // Zoom:1 forces hasLayout, display:inline gives inline behavior.
    style.zoom = '1';
    style.display = 'inline';
  } else if (goog.userAgent.GECKO) {
    // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
    // is close enough.
    style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
        '-moz-inline-box';
  } else {
    // Opera, Webkit, and Safari seem to do OK with the standard inline-block
    // style.
    style.display = 'inline-block';
  }
};


/**
 * Returns true if the element is using right to left (rtl) direction.
 * @param {Element} el  The element to test.
 * @return {boolean} True for right to left, false for left to right.
 */
goog.style.isRightToLeft = function(el) {
  return 'rtl' == goog.style.getStyle_(el, 'direction');
};


/**
 * The CSS style property corresponding to an element being
 * unselectable on the current browser platform (null if none).
 * Opera and IE instead use a DOM attribute 'unselectable'.
 * @type {?string}
 * @private
 */
goog.style.unselectableStyle_ =
    goog.userAgent.GECKO ? 'MozUserSelect' :
    goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
    null;


/**
 * Returns true if the element is set to be unselectable, false otherwise.
 * Note that on some platforms (e.g. Mozilla), even if an element isn't set
 * to be unselectable, it will behave as such if any of its ancestors is
 * unselectable.
 * @param {Element} el  Element to check.
 * @return {boolean}  Whether the element is set to be unselectable.
 */
goog.style.isUnselectable = function(el) {
  if (goog.style.unselectableStyle_) {
    return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
    return el.getAttribute('unselectable') == 'on';
  }
  return false;
};


/**
 * Makes the element and its descendants selectable or unselectable.  Note
 * that on some platforms (e.g. Mozilla), even if an element isn't set to
 * be unselectable, it will behave as such if any of its ancestors is
 * unselectable.
 * @param {Element} el  The element to alter.
 * @param {boolean} unselectable  Whether the element and its descendants
 *     should be made unselectable.
 * @param {boolean=} opt_noRecurse  Whether to only alter the element's own
 *     selectable state, and leave its descendants alone; defaults to false.
 */
goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
  // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
  var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
  var name = goog.style.unselectableStyle_;
  if (name) {
    // Add/remove the appropriate CSS style to/from the element and its
    // descendants.
    var value = unselectable ? 'none' : '';
    el.style[name] = value;
    if (descendants) {
      for (var i = 0, descendant; descendant = descendants[i]; i++) {
        descendant.style[name] = value;
      }
    }
  } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
    // Toggle the 'unselectable' attribute on the element and its descendants.
    var value = unselectable ? 'on' : '';
    el.setAttribute('unselectable', value);
    if (descendants) {
      for (var i = 0, descendant; descendant = descendants[i]; i++) {
        descendant.setAttribute('unselectable', value);
      }
    }
  }
};


/**
 * Gets the border box size for an element.
 * @param {Element} element  The element to get the size for.
 * @return {!goog.math.Size} The border box size.
 */
goog.style.getBorderBoxSize = function(element) {
  return new goog.math.Size(element.offsetWidth, element.offsetHeight);
};


/**
 * Sets the border box size of an element. This is potentially expensive in IE
 * if the document is CSS1Compat mode
 * @param {Element} element  The element to set the size on.
 * @param {goog.math.Size} size  The new size.
 */
goog.style.setBorderBoxSize = function(element, size) {
  var doc = goog.dom.getOwnerDocument(element);
  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();

  if (goog.userAgent.IE &&
      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
    var style = element.style;
    if (isCss1CompatMode) {
      var paddingBox = goog.style.getPaddingBox(element);
      var borderBox = goog.style.getBorderBox(element);
      style.pixelWidth = size.width - borderBox.left - paddingBox.left -
                         paddingBox.right - borderBox.right;
      style.pixelHeight = size.height - borderBox.top - paddingBox.top -
                          paddingBox.bottom - borderBox.bottom;
    } else {
      style.pixelWidth = size.width;
      style.pixelHeight = size.height;
    }
  } else {
    goog.style.setBoxSizingSize_(element, size, 'border-box');
  }
};


/**
 * Gets the content box size for an element.  This is potentially expensive in
 * all browsers.
 * @param {Element} element  The element to get the size for.
 * @return {!goog.math.Size} The content box size.
 */
goog.style.getContentBoxSize = function(element) {
  var doc = goog.dom.getOwnerDocument(element);
  var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
  if (ieCurrentStyle &&
      goog.dom.getDomHelper(doc).isCss1CompatMode() &&
      ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
      !ieCurrentStyle.boxSizing) {
    // If IE in CSS1Compat mode than just use the width and height.
    // If we have a boxSizing then fall back on measuring the borders etc.
    var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
                                            'width', 'pixelWidth');
    var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
                                             'height', 'pixelHeight');
    return new goog.math.Size(width, height);
  } else {
    var borderBoxSize = goog.style.getBorderBoxSize(element);
    var paddingBox = goog.style.getPaddingBox(element);
    var borderBox = goog.style.getBorderBox(element);
    return new goog.math.Size(borderBoxSize.width -
                              borderBox.left - paddingBox.left -
                              paddingBox.right - borderBox.right,
                              borderBoxSize.height -
                              borderBox.top - paddingBox.top -
                              paddingBox.bottom - borderBox.bottom);
  }
};


/**
 * Sets the content box size of an element. This is potentially expensive in IE
 * if the document is BackCompat mode.
 * @param {Element} element  The element to set the size on.
 * @param {goog.math.Size} size  The new size.
 */
goog.style.setContentBoxSize = function(element, size) {
  var doc = goog.dom.getOwnerDocument(element);
  var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
  if (goog.userAgent.IE &&
      (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
    var style = element.style;
    if (isCss1CompatMode) {
      style.pixelWidth = size.width;
      style.pixelHeight = size.height;
    } else {
      var paddingBox = goog.style.getPaddingBox(element);
      var borderBox = goog.style.getBorderBox(element);
      style.pixelWidth = size.width + borderBox.left + paddingBox.left +
                         paddingBox.right + borderBox.right;
      style.pixelHeight = size.height + borderBox.top + paddingBox.top +
                          paddingBox.bottom + borderBox.bottom;
    }
  } else {
    goog.style.setBoxSizingSize_(element, size, 'content-box');
  }
};


/**
 * Helper function that sets the box sizing as well as the width and height
 * @param {Element} element  The element to set the size on.
 * @param {goog.math.Size} size  The new size to set.
 * @param {string} boxSizing  The box-sizing value.
 * @private
 */
goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
  var style = element.style;
  if (goog.userAgent.GECKO) {
    style.MozBoxSizing = boxSizing;
  } else if (goog.userAgent.WEBKIT) {
    style.WebkitBoxSizing = boxSizing;
  } else {
    // Includes IE8 and Opera 9.50+
    style.boxSizing = boxSizing;
  }

  // Setting this to a negative value will throw an exception on IE
  // (and doesn't do anything different than setting it to 0).
  style.width = Math.max(size.width, 0) + 'px';
  style.height = Math.max(size.height, 0) + 'px';
};


/**
 * IE specific function that converts a non pixel unit to pixels.
 * @param {Element} element  The element to convert the value for.
 * @param {string} value  The current value as a string. The value must not be
 *     ''.
 * @param {string} name  The CSS property name to use for the converstion. This
 *     should be 'left', 'top', 'width' or 'height'.
 * @param {string} pixelName  The CSS pixel property name to use to get the
 *     value in pixels.
 * @return {number} The value in pixels.
 * @private
 */
goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
  // Try if we already have a pixel value. IE does not do half pixels so we
  // only check if it matches a number followed by 'px'.
  if (/^\d+px?$/.test(value)) {
    return parseInt(value, 10);
  } else {
    var oldStyleValue = element.style[name];
    var oldRuntimeValue = element.runtimeStyle[name];
    // set runtime style to prevent changes
    element.runtimeStyle[name] = element.currentStyle[name];
    element.style[name] = value;
    var pixelValue = element.style[pixelName];
    // restore
    element.style[name] = oldStyleValue;
    element.runtimeStyle[name] = oldRuntimeValue;
    return pixelValue;
  }
};


/**
 * Helper function for getting the pixel padding or margin for IE.
 * @param {Element} element  The element to get the padding for.
 * @param {string} propName  The property name.
 * @return {number} The pixel padding.
 * @private
 */
goog.style.getIePixelDistance_ = function(element, propName) {
  var value = goog.style.getCascadedStyle(element, propName);
  return value ?
      goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
};


/**
 * Gets the computed paddings or margins (on all sides) in pixels.
 * @param {Element} element  The element to get the padding for.
 * @param {string} stylePrefix  Pass 'padding' to retrieve the padding box,
 *     or 'margin' to retrieve the margin box.
 * @return {!goog.math.Box} The computed paddings or margins.
 * @private
 */
goog.style.getBox_ = function(element, stylePrefix) {
  if (goog.userAgent.IE) {
    var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
    var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
    var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
    var bottom = goog.style.getIePixelDistance_(
        element, stylePrefix + 'Bottom');
    return new goog.math.Box(top, right, bottom, left);
  } else {
    // On non-IE browsers, getComputedStyle is always non-null.
    var left = /** @type {string} */ (
        goog.style.getComputedStyle(element, stylePrefix + 'Left'));
    var right = /** @type {string} */ (
        goog.style.getComputedStyle(element, stylePrefix + 'Right'));
    var top = /** @type {string} */ (
        goog.style.getComputedStyle(element, stylePrefix + 'Top'));
    var bottom = /** @type {string} */ (
        goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));

    // NOTE(arv): Gecko can return floating point numbers for the computed
    // style values.
    return new goog.math.Box(parseFloat(top),
                             parseFloat(right),
                             parseFloat(bottom),
                             parseFloat(left));
  }
};


/**
 * Gets the computed paddings (on all sides) in pixels.
 * @param {Element} element  The element to get the padding for.
 * @return {!goog.math.Box} The computed paddings.
 */
goog.style.getPaddingBox = function(element) {
  return goog.style.getBox_(element, 'padding');
};


/**
 * Gets the computed margins (on all sides) in pixels.
 * @param {Element} element  The element to get the margins for.
 * @return {!goog.math.Box} The computed margins.
 */
goog.style.getMarginBox = function(element) {
  return goog.style.getBox_(element, 'margin');
};


/**
 * A map used to map the border width keywords to a pixel width.
 * @type {Object}
 * @private
 */
goog.style.ieBorderWidthKeywords_ = {
  'thin': 2,
  'medium': 4,
  'thick': 6
};


/**
 * Helper function for IE to get the pixel border.
 * @param {Element} element  The element to get the pixel border for.
 * @param {string} prop  The part of the property name.
 * @return {number} The value in pixels.
 * @private
 */
goog.style.getIePixelBorder_ = function(element, prop) {
  if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
    return 0;
  }
  var width = goog.style.getCascadedStyle(element, prop + 'Width');
  if (width in goog.style.ieBorderWidthKeywords_) {
    return goog.style.ieBorderWidthKeywords_[width];
  }
  return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
};


/**
 * Gets the computed border widths (on all sides) in pixels
 * @param {Element} element  The element to get the border widths for.
 * @return {!goog.math.Box} The computed border widths.
 */
goog.style.getBorderBox = function(element) {
  if (goog.userAgent.IE) {
    var left = goog.style.getIePixelBorder_(element, 'borderLeft');
    var right = goog.style.getIePixelBorder_(element, 'borderRight');
    var top = goog.style.getIePixelBorder_(element, 'borderTop');
    var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
    return new goog.math.Box(top, right, bottom, left);
  } else {
    // On non-IE browsers, getComputedStyle is always non-null.
    var left = /** @type {string} */ (
        goog.style.getComputedStyle(element, 'borderLeftWidth'));
    var right = /** @type {string} */ (
        goog.style.getComputedStyle(element, 'borderRightWidth'));
    var top = /** @type {string} */ (
        goog.style.getComputedStyle(element, 'borderTopWidth'));
    var bottom = /** @type {string} */ (
        goog.style.getComputedStyle(element, 'borderBottomWidth'));

    return new goog.math.Box(parseFloat(top),
                             parseFloat(right),
                             parseFloat(bottom),
                             parseFloat(left));
  }
};


/**
 * Returns the font face applied to a given node. Opera and IE should return
 * the font actually displayed. Firefox returns the author's most-preferred
 * font (whether the browser is capable of displaying it or not.)
 * @param {Element} el  The element whose font family is returned.
 * @return {string} The font family applied to el.
 */
goog.style.getFontFamily = function(el) {
  var doc = goog.dom.getOwnerDocument(el);
  var font = '';
  if (doc.body.createTextRange) {
    var range = doc.body.createTextRange();
    range.moveToElementText(el);
    /** @preserveTry */
    try {
      font = range.queryCommandValue('FontName');
    } catch (e) {
      // This is a workaround for a awkward exception.
      // On some IE, there is an exception coming from it.
      // The error description from this exception is:
      // This window has already been registered as a drop target
      // This is bogus description, likely due to a bug in ie.
      font = '';
    }
  }
  if (!font) {
    // Note if for some reason IE can't derive FontName with a TextRange, we
    // fallback to using currentStyle
    font = goog.style.getStyle_(el, 'fontFamily');
  }

  // Firefox returns the applied font-family string (author's list of
  // preferred fonts.) We want to return the most-preferred font, in lieu of
  // the *actually* applied font.
  var fontsArray = font.split(',');
  if (fontsArray.length > 1) font = fontsArray[0];

  // Sanitize for x-browser consistency:
  // Strip quotes because browsers aren't consistent with how they're
  // applied; Opera always encloses, Firefox sometimes, and IE never.
  return goog.string.stripQuotes(font, '"\'');
};


/**
 * Regular expression used for getLengthUnits.
 * @type {RegExp}
 * @private
 */
goog.style.lengthUnitRegex_ = /[^\d]+$/;


/**
 * Returns the units used for a CSS length measurement.
 * @param {string} value  A CSS length quantity.
 * @return {?string} The units of measurement.
 */
goog.style.getLengthUnits = function(value) {
  var units = value.match(goog.style.lengthUnitRegex_);
  return units && units[0] || null;
};


/**
 * Map of absolute CSS length units
 * @type {Object}
 * @private
 */
goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
  'cm' : 1,
  'in' : 1,
  'mm' : 1,
  'pc' : 1,
  'pt' : 1
};


/**
 * Map of relative CSS length units that can be accurately converted to px
 * font-size values using getIePixelValue_. Only units that are defined in
 * relation to a font size are convertible (%, small, etc. are not).
 * @type {Object}
 * @private
 */
goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
  'em' : 1,
  'ex' : 1
};


/**
 * Returns the font size, in pixels, of text in an element.
 * @param {Element} el  The element whose font size is returned.
 * @return {number} The font size (in pixels).
 */
goog.style.getFontSize = function(el) {
  var fontSize = goog.style.getStyle_(el, 'fontSize');
  var sizeUnits = goog.style.getLengthUnits(fontSize);
  if (fontSize && 'px' == sizeUnits) {
    // NOTE(user): This could be parseFloat instead, but IE doesn't return
    // decimal fractions in getStyle_ and Firefox reports the fractions, but
    // ignores them when rendering. Interestingly enough, when we force the
    // issue and size something to e.g., 50% of 25px, the browsers round in
    // opposite directions with Firefox reporting 12px and IE 13px. I punt.
    return parseInt(fontSize, 10);
  }

  // In IE, we can convert absolute length units to a px value using
  // goog.style.getIePixelValue_. Units defined in relation to a font size
  // (em, ex) are applied relative to the element's parentNode and can also
  // be converted.
  if (goog.userAgent.IE) {
    if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
      return goog.style.getIePixelValue_(el,
                                         fontSize,
                                         'left',
                                         'pixelLeft');
    } else if (el.parentNode &&
               el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
               sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
      // Check the parent size - if it is the same it means the relative size
      // value is inherited and we therefore don't want to count it twice.  If
      // it is different, this element either has explicit style or has a CSS
      // rule applying to it.
      var parentElement = /** @type {Element} */ (el.parentNode);
      var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
      return goog.style.getIePixelValue_(parentElement,
                                         fontSize == parentSize ?
                                             '1em' : fontSize,
                                         'left',
                                         'pixelLeft');
    }
  }

  // Sometimes we can't cleanly find the font size (some units relative to a
  // node's parent's font size are difficult: %, smaller et al), so we create
  // an invisible, absolutely-positioned span sized to be the height of an 'M'
  // rendered in its parent's (i.e., our target element's) font size. This is
  // the definition of CSS's font size attribute.
  var sizeElement = goog.dom.createDom(
      'span',
      {'style': 'visibility:hidden;position:absolute;' +
            'line-height:0;padding:0;margin:0;border:0;height:1em;'});
  goog.dom.appendChild(el, sizeElement);
  fontSize = sizeElement.offsetHeight;
  goog.dom.removeNode(sizeElement);

  return fontSize;
};


/**
 * Parses a style attribute value.  Converts CSS property names to camel case.
 * @param {string} value The style attribute value.
 * @return {!Object} Map of CSS properties to string values.
 */
goog.style.parseStyleAttribute = function(value) {
  var result = {};
  goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
    var keyValue = pair.split(/\s*:\s*/);
    if (keyValue.length == 2) {
      result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
    }
  });
  return result;
};


/**
 * Reverse of parseStyleAttribute; that is, takes a style object and returns the
 * corresponding attribute value.  Converts camel case property names to proper
 * CSS selector names.
 * @param {Object} obj Map of CSS properties to values.
 * @return {string} The style attribute value.
 */
goog.style.toStyleAttribute = function(obj) {
  var buffer = [];
  goog.object.forEach(obj, function(value, key) {
    buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
  });
  return buffer.join('');
};


/**
 * Sets CSS float property on an element.
 * @param {Element} el The element to set float property on.
 * @param {string} value The value of float CSS property to set on this element.
 */
goog.style.setFloat = function(el, value) {
  el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
};


/**
 * Gets value of explicitly-set float CSS property on an element.
 * @param {Element} el The element to get float property of.
 * @return {string} The value of explicitly-set float CSS property on this
 *     element.
 */
goog.style.getFloat = function(el) {
  return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
};


/**
 * Returns the scroll bar width (represents the width of both horizontal
 * and vertical scroll).
 *
 * @param {string=} opt_className An optional class name (or names) to apply
 *     to the invisible div created to measure the scrollbar. This is necessary
 *     if some scrollbars are styled differently than others.
 * @return {number} The scroll bar width in px.
 */
goog.style.getScrollbarWidth = function(opt_className) {
  // Add two hidden divs.  The child div is larger than the parent and
  // forces scrollbars to appear on it.
  // Using overflow:scroll does not work consistently with scrollbars that
  // are styled with ::-webkit-scrollbar.
  var outerDiv = goog.dom.createElement('div');
  if (opt_className) {
    outerDiv.className = opt_className;
  }
  outerDiv.style.cssText = 'overflow:auto;' +
      'position:absolute;top:0;width:100px;height:100px';
  var innerDiv = goog.dom.createElement('div');
  goog.style.setSize(innerDiv, '200px', '200px');
  outerDiv.appendChild(innerDiv);
  goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
  var width = outerDiv.offsetWidth - outerDiv.clientWidth;
  goog.dom.removeNode(outerDiv);
  return width;
};


/**
 * Regular expression to extract x and y translation components from a CSS
 * transform Matrix representation.
 *
 * @type {!RegExp}
 * @const
 * @private
 */
goog.style.MATRIX_TRANSLATION_REGEX_ =
    new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
               '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
               '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');


/**
 * Returns the x,y translation component of any CSS transforms applied to the
 * element, in pixels.
 *
 * @param {!Element} element The element to get the translation of.
 * @return {!goog.math.Coordinate} The CSS translation of the element in px.
 */
goog.style.getCssTranslation = function(element) {
  var property;
  if (goog.userAgent.IE) {
    property = '-ms-transform';
  } else if (goog.userAgent.WEBKIT) {
    property = '-webkit-transform';
  } else if (goog.userAgent.OPERA) {
    property = '-o-transform';
  } else if (goog.userAgent.GECKO) {
    property = '-moz-transform';
  }
  var transform;
  if (property) {
    transform = goog.style.getStyle_(element, property);
  }
  if (!transform) {
    transform = goog.style.getStyle_(element, 'transform');
  }
  if (!transform) {
    return new goog.math.Coordinate(0, 0);
  }
  var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
  if (!matches) {
    return new goog.math.Coordinate(0, 0);
  }
  return new goog.math.Coordinate(parseFloat(matches[1]),
                                  parseFloat(matches[2]));
};

// Input 163
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Simple logger that logs a Div Element.
 *
 */

goog.provide('goog.debug.DivConsole');

goog.require('goog.debug.HtmlFormatter');
goog.require('goog.debug.LogManager');
goog.require('goog.style');



/**
 * A class for visualising logger calls in a div element.
 * @param {Element} element The element to append to.
 * @constructor
 */
goog.debug.DivConsole = function(element) {
  this.publishHandler_ = goog.bind(this.addLogRecord, this);
  this.formatter_ = new goog.debug.HtmlFormatter();
  this.formatter_.showAbsoluteTime = false;
  this.isCapturing_ = false;
  this.element_ = element;
  this.elementOwnerDocument_ =
      this.element_.ownerDocument || this.element_.document;

  this.installStyles();
};


/**
 * Installs styles for the log messages and its div
 */
goog.debug.DivConsole.prototype.installStyles = function() {
  goog.style.installStyles(
      '.dbg-sev{color:#F00}' +
      '.dbg-w{color:#C40}' +
      '.dbg-sh{font-weight:bold;color:#000}' +
      '.dbg-i{color:#444}' +
      '.dbg-f{color:#999}' +
      '.dbg-ev{color:#0A0}' +
      '.dbg-m{color:#990}' +
      '.logmsg{border-bottom:1px solid #CCC;padding:2px}' +
      '.logsep{background-color: #8C8;}' +
      '.logdiv{border:1px solid #CCC;background-color:#FCFCFC;' +
      'font:medium monospace}',
      this.element_);
  this.element_.className += ' logdiv';
};


/**
 * Sets whether we are currently capturing logger output.
 * @param {boolean} capturing Whether to capture logger output.
 */
goog.debug.DivConsole.prototype.setCapturing = function(capturing) {
  if (capturing == this.isCapturing_) {
    return;
  }

  // attach or detach handler from the root logger
  var rootLogger = goog.debug.LogManager.getRoot();
  if (capturing) {
    rootLogger.addHandler(this.publishHandler_);
  } else {
    rootLogger.removeHandler(this.publishHandler_);
    this.logBuffer = '';
  }
  this.isCapturing_ = capturing;
};


/**
 * Adds a log record.
 * @param {goog.debug.LogRecord} logRecord The log entry.
 */
goog.debug.DivConsole.prototype.addLogRecord = function(logRecord) {
  var scroll = this.element_.scrollHeight - this.element_.scrollTop -
      this.element_.clientHeight <= 100;

  var div = this.elementOwnerDocument_.createElement('div');
  div.className = 'logmsg';
  div.innerHTML = this.formatter_.formatRecord(logRecord);
  this.element_.appendChild(div);

  if (scroll) {
    this.element_.scrollTop = this.element_.scrollHeight;
  }
};


/**
 * Gets the formatter for outputting to the console. The default formatter
 * is an instance of goog.debug.HtmlFormatter
 * @return {goog.debug.Formatter} The formatter in use.
 */
goog.debug.DivConsole.prototype.getFormatter = function() {
  return this.formatter_;
};


/**
 * Sets the formatter for outputting to the console.
 * @param {goog.debug.Formatter} formatter The formatter to use.
 */
goog.debug.DivConsole.prototype.setFormatter = function(formatter) {
  this.formatter_ = formatter;
};


/**
 * Adds a separator to the debug window.
 */
goog.debug.DivConsole.prototype.addSeparator = function() {
  var div = this.elementOwnerDocument_.createElement('div');
  div.className = 'logmsg logsep';
  this.element_.appendChild(div);
};


/**
 * Clears the console.
 */
goog.debug.DivConsole.prototype.clear = function() {
  this.element_.innerHTML = '';
};

// Input 164
/**
 * @fileoverview Utilities function for debug.
 *
 * NOTE: these code are stripped using compiler prefix feature.
 * See more detail in tools/strip_debug.txt file.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.debug');

goog.require('goog.debug.Console');
goog.require('goog.debug.DivConsole');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('goog.events.BrowserEvent'); // cause compile error if not.


/**
 * General debug console
 * @type {goog.debug.Console}
 */
ydn.debug.logger_console = null;


/**
 *
 * @type {goog.debug.DivConsole}
 */
ydn.debug.logger_div = null;


/**
 * @type {goog.debug.Logger} logger.
 */
ydn.debug.logger = goog.debug.Logger.getLogger('ydn.debug');


/**
 * Predefined level are: 'ALL' (0) 'FINEST' (300), 'FINER' (ALL), 'FINE' (500),
 * 'CONFIG' (700), 'INFO' (800), 'WARNING', (900)
 * @param {string=} scope eg: 'ydn.db'.
 * @param {string|number=} level
 * @param {Element=} ele display target DIV. If not provided, it log to console.
 */
ydn.debug.log = function(scope, level, ele) {

  scope = scope || 'ydn';
  var log_level = goog.isNumber(level) ? new goog.debug.Logger.Level(
    'log', level) :
      goog.isString(level) ? goog.debug.Logger.Level.getPredefinedLevel(
        level.toUpperCase()) :
          goog.debug.Logger.Level.FINE;

  goog.debug.Logger.getLogger(scope).setLevel(log_level);


  if (goog.isDef(ele)) {
    if (!ydn.debug.logger_div) {
      ydn.debug.logger_div = new goog.debug.DivConsole(ele);
      ydn.debug.logger_div.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  } else {
    if (!ydn.debug.logger_console && !ydn.debug.logger_div) {
      ydn.debug.logger_console = new goog.debug.Console();
      ydn.debug.logger_console.setCapturing(true);
      goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.WARNING);
    }
  }
};


// Input 165
/**
 * Created with IntelliJ IDEA.
 * User: kyawtun
 * Date: 12/21/12
 * Time: 9:49 AM
 * To change this template use File | Settings | File Templates.
 */

goog.provide('ydn.debug.exports');
goog.require('ydn.debug');


goog.exportSymbol('ydn.debug.log', ydn.debug.log);

// Input 166
/**
 * @fileoverview Exports for ydn-db CORE module.
 *
 */


goog.provide('ydn.db.Storage.main_entry');
goog.require('ydn.base.exports');
goog.require('ydn.db.Storage');
goog.require('ydn.db.algo.exports');
goog.require('ydn.db.con.exports');
goog.require('ydn.db.core.exports');
goog.require('ydn.db.crud.exports');
goog.require('ydn.db.sql.exports');
goog.require('ydn.db.tr.events.exports');
goog.require('ydn.debug.exports');


goog.exportSymbol('ydn.db.Storage', ydn.db.Storage);



                ydn.json.POLY_FILL = true;
            